asciify-engine 4.13.0 → 4.14.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 -0
- package/dist/characters-BgG9AZQl.d.cts +72 -0
- package/dist/characters-BgG9AZQl.d.ts +72 -0
- package/dist/core.d.cts +2 -1
- package/dist/core.d.ts +2 -1
- package/dist/field.cjs +1 -1
- package/dist/field.d.cts +6 -0
- package/dist/field.d.ts +6 -0
- package/dist/field.js +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/kit.cjs +1 -1
- package/dist/kit.d.cts +270 -7
- package/dist/kit.d.ts +270 -7
- package/dist/kit.js +1 -1
- package/dist/{model-B0jK31nS.d.cts → model-9SbRVqqI.d.cts} +1 -72
- package/dist/{model-BARiWJks.d.ts → model-DXHhQhxP.d.ts} +1 -72
- package/dist/studio.d.cts +3 -2
- package/dist/studio.d.ts +3 -2
- package/package.json +1 -1
- package/skills/asciify-engine/SKILL.md +74 -48
- package/skills/asciify-engine/references/applying-asciify.md +234 -0
- package/skills/asciify-engine/references/components.md +1 -1
- package/skills/asciify-engine/references/design-matching.md +10 -7
- package/skills/asciify-engine/references/glyph-field.md +3 -0
- package/skills/asciify-engine/references/kit.md +112 -26
- package/skills/asciify-engine/references/media-api.md +2 -2
- package/skills/asciify-engine/references/project-integration.md +1 -1
- package/skills/asciify-engine/references/scenario-prompts.md +1 -1
- package/skills/asciify-engine/references/upgrading.md +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { beam, decode, odometer, buttonLoading, statusOrb, skeleton, pill, tabs, imageGenerate } from 'asciify-engine/kit';
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Imports are tree-shaken: one component costs about
|
|
9
|
+
Imports are tree-shaken: one component costs about 6–8.5 KB gzip on top of `asciify-engine/field`, which the kit shares rather than bundling again.
|
|
10
10
|
|
|
11
11
|
## Shared options and handle
|
|
12
12
|
|
|
@@ -17,8 +17,11 @@ Every function takes these options:
|
|
|
17
17
|
| `ink` | per component | A CSS colour, a custom property (`'--brand'`) or `'var(--brand, #888)'`. Re-read when the theme changes (media query, or `class`/`data-theme` on `<html>` or `<body>`). Defaults pick light or dark ink from the element's own opaque background, else from its text colour. |
|
|
18
18
|
| `look` | none | Raw glyph-field settings layered over the component's look: `charset`, `cellSize`, `font` and so on. |
|
|
19
19
|
| `reducedMotion` | the visitor's setting | `true` or `false` to override `prefers-reduced-motion`. Reduced motion draws each state settled, in one frame. |
|
|
20
|
+
| `lazy` | `true` | Mount the field only near the viewport and release it far from it. `false` mounts at once, wherever the element is. See Performance and integration. |
|
|
21
|
+
| `poster` | none | A still image under the field: shown until the first GPU frame, while the field is released, and in its place without WebGL. |
|
|
22
|
+
| `globalLook` | `true` | `false` keeps this field's own characters when `setGlobalLook` re-skins the page. |
|
|
20
23
|
|
|
21
|
-
Every handle has `supported`, `field` (the underlying `GlyphField`, or `null`), `update(options)`, `play()`, `pause()` and `destroy()`. Components add their own methods, listed below.
|
|
24
|
+
Every handle has `supported`, `field` (the underlying `GlyphField`, or `null` without WebGL), `update(options)`, `play()`, `pause()` and `destroy()`. `update` merges, and `look` merges key by key, so `update({ look: { cellSize: 6 } })` keeps an earlier `charset`; a look key set to `undefined` is removed, so the component's own value comes back. `update({ poster })` points the poster at a new still, such as the other theme's, without remounting. Components add their own methods, listed below.
|
|
22
25
|
|
|
23
26
|
In React, create the handle in an effect and return `destroy`:
|
|
24
27
|
|
|
@@ -37,18 +40,18 @@ b.success(); // the ring closes, a flash sweeps round, a green fade settles
|
|
|
37
40
|
b.error(); // stops dead, flickers red twice, cracks into dim red fragments until reset() or progress()
|
|
38
41
|
```
|
|
39
42
|
|
|
40
|
-
Options: `palette` (`'colorful' | 'mono' | 'ocean' | 'sunset'` or your own hex stops, default `colorful`), `mode` (`'rotate'` two turning arcs, `'pulse'` a breathing border, `'progress'` all but dark at rest and a progress ring while running; default `rotate`; modes crossfade when changed), `grain` (`'dots' | 'glyphs'`), `speed` (lap multiplier, default 1), `hover` (faster and brighter on hover and visible focus, default true), `depth` (glow depth in px, default 18; it also stops 2px short of the padding). Methods: `progress(0..1)` (in `rotate` and `pulse` the beam speeds up as it fills), `success()`, `error()`, `reset()`. Reduced motion draws one settled frame per state. There is no bloom outside the border.
|
|
43
|
+
Options: `palette` (`'colorful' | 'mono' | 'ocean' | 'sunset'` or your own hex stops, default `colorful`), `mode` (`'rotate'` two turning arcs, `'pulse'` a breathing border, `'progress'` all but dark at rest and a progress ring while running; default `rotate`; modes crossfade when changed), `grain` (`'dots' | 'glyphs'`), `speed` (lap multiplier, default 1), `hover` (faster and brighter on hover and visible focus, default true; pointer moves count too, so a pointer already over the element or a scripted one heats it), `depth` (glow depth in px, default 18; it also stops 2px short of the padding). Methods: `progress(0..1)` (in `rotate` and `pulse` the beam speeds up as it fills), `success()`, `error()`, `reset()`. Reduced motion draws one settled frame per state. There is no bloom outside the border.
|
|
41
44
|
|
|
42
45
|
## decode
|
|
43
46
|
|
|
44
47
|
A headline that condenses out of a drifting field, left to right. By default it draws the element's own text exactly where the browser laid it out, and keeps the real text in the DOM for selection and screen readers.
|
|
45
48
|
|
|
46
49
|
```ts
|
|
47
|
-
const d = decode(document.querySelector('h1')!, { duration:
|
|
50
|
+
const d = decode(document.querySelector('h1')!, { duration: 800 });
|
|
48
51
|
d.replay();
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
Options: `text` (draw this instead, centred and as large as fits; `\n` breaks lines), `duration` (ms, default 900), `trigger` (`'visible' | 'manual'`), `replayOnHover` (default true), `settle` (`'glyphs'` keeps the headline in characters, `'text'` hands back to the crisp HTML). Ink defaults to the element's text colour. Method: `replay()`.
|
|
54
|
+
Options: `text` (draw this instead, centred and as large as fits, in the element's own font weight, family and letter spacing; `\n` breaks lines), `duration` (ms, default 900), `trigger` (`'visible' | 'manual'`), `replayOnHover` (default true; entry is read from pointer moves too, so a scripted cursor replays it), `settle` (`'glyphs'` keeps the headline in characters, `'text'` hands back to the crisp HTML). Ink defaults to the element's text colour. Method: `replay()`.
|
|
52
55
|
|
|
53
56
|
## odometer
|
|
54
57
|
|
|
@@ -59,7 +62,7 @@ const o = odometer(figure, { value: 48290, format: n => `$${n.toLocaleString('en
|
|
|
59
62
|
o.set(49530);
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
Options: `value` (default: parsed from the element's text), `format` (default `toLocaleString()`), `duration` (ms the first column rolls, default 360), `stagger` (ms longer per column, default 60), `glyphs` (default `#%*+=-:`). Methods: `set(n)`; getter `value`.
|
|
65
|
+
Options: `value` (default: parsed from the element's text), `format` (default `toLocaleString()`), `duration` (ms the first column rolls, default 360), `stagger` (ms longer per column, default 60), `glyphs` (default `#%*+=-:`), `face` (`'text'`: crisp text at rest, characters only while a column rolls; `'glyph'`: the whole figure drawn in characters, at rest too; default `text`, and `update({ face })` switches it live). Methods: `set(n)`; getter `value`.
|
|
63
66
|
|
|
64
67
|
## buttonLoading
|
|
65
68
|
|
|
@@ -71,7 +74,7 @@ b.start('Deploying');
|
|
|
71
74
|
await deploy().then(() => b.succeed('Deployed'), () => b.fail('Retry'));
|
|
72
75
|
```
|
|
73
76
|
|
|
74
|
-
Options: `text` (idle label, default the label's text), `label` (the element holding the text, when the button also has an icon), `check` (a check before the success label, default true), `shake` (on failure, default true), `announce` (a live region to write each state to). Methods: `start(label?)`, `succeed(label?)`, `fail(label?)`, `reset(label?)`; getter `state`. Sets `aria-busy` and `data-state` on the button. Without WebGL the methods still change the label text.
|
|
77
|
+
Options: `text` (idle label, default the label's text), `label` (the element holding the text, when the button also has an icon), `check` (a crisp SVG check before the success label, default true), `icon` (an icon-only button's icon, default its first `svg` when it has no text: one cell breathes through the glyphs over it while loading, and a check takes its place on success; the button's `aria-label` follows each state), `shake` (on failure, default true), `announce` (a live region to write each state to). Methods: `start(label?)`, `succeed(label?)`, `fail(label?)`, `reset(label?)`; getter `state`. Sets `aria-busy` and `data-state` on the button. Without WebGL the methods still change the label text.
|
|
75
78
|
|
|
76
79
|
## statusOrb
|
|
77
80
|
|
|
@@ -95,7 +98,7 @@ await fetchPost();
|
|
|
95
98
|
await s.resolve();
|
|
96
99
|
```
|
|
97
100
|
|
|
98
|
-
Options: `blocks` (selector, default `[data-block]`), `shimmer` (ms per sweep, default 1250), `resolve` (ms per block, default 240), `stagger` (ms between blocks unless `data-delay` is set, default 60), `develop` (ms an image develops, default 560). Methods: `resolve()` (a promise for the last block), `reset()`; getter `loading`. Sets `aria-busy` on the element while loading.
|
|
101
|
+
Options: `blocks` (selector, default `[data-block]`; `reset()` fades loaded content out under the returning skeleton), `shimmer` (ms per sweep, default 1250), `resolve` (ms per block, default 240), `stagger` (ms between blocks unless `data-delay` is set, default 60), `develop` (ms an image develops, default 560). Methods: `resolve()` (a promise for the last block), `reset()`; getter `loading`. Sets `aria-busy` on the element while loading.
|
|
99
102
|
|
|
100
103
|
## pill
|
|
101
104
|
|
|
@@ -114,7 +117,7 @@ pills.refresh(); // tone changes ease over 240ms
|
|
|
114
117
|
pills.count(14); // only the changed digits roll
|
|
115
118
|
```
|
|
116
119
|
|
|
117
|
-
Kinds: `data-count` makes a count pill, `data-tone="new"` a sheen pill, anything else a status pill. Options: `tone` (fallback for `data-tone`: `green | amber | red | neutral | new`, default `neutral`), `live` (fallback for `data-live`: the indicator breathes from `.#.` to `*#*` and a sweep crosses every few seconds; default false), `indicator` (draw the indicator over the pill's `[data-dot]`, adding a 15px one when missing; default true), `sheen` (
|
|
120
|
+
Kinds: `data-count` makes a count pill, `data-tone="new"` a sheen pill, anything else a status pill. Options: `tone` (fallback for `data-tone`: `green | amber | red | neutral | new`, default `neutral`), `live` (fallback for `data-live`: the indicator breathes from `.#.` to `*#*` and a sweep crosses every few seconds; default false), `indicator` (draw the indicator over the pill's `[data-dot]`, adding a 15px one when missing; default true), `sheen` (when the pointer moves onto a `new` pill, default true), `tones` (`{ light, dark }` maps of `[r, g, b]` glyph hues). Methods: `sheen(pill?)`, `count(value, pill?)`, `refresh()`. Count pills keep a visually hidden copy of the number for screen readers.
|
|
118
121
|
|
|
119
122
|
## tabs
|
|
120
123
|
|
|
@@ -148,6 +151,36 @@ const s = mountSurface({ host: el, look: () => ({ mark: 'glyph', cellSize: 6 }),
|
|
|
148
151
|
paint: ({ pen, w, h, now, still }) => { pen.fillStyle = '#fff'; pen.fillRect(0, 0, (w * (now % 1000)) / 1000, h); return !still; } });
|
|
149
152
|
```
|
|
150
153
|
|
|
154
|
+
## Performance and integration
|
|
155
|
+
|
|
156
|
+
A page can hold every kit component at once. The shared core keeps the GPU cost to what is on screen:
|
|
157
|
+
|
|
158
|
+
- **Lazy mounting.** A field mounts when its element comes within half a viewport of the screen and is released, freeing its WebGL context, beyond one viewport. Mounts that cost more than about 4 ms in a frame wait for the next one, nearest the screen first. The handle survives release and remount: its methods keep working, `update()` calls made while released apply on the next mount, `pause()` holds, and the component paints its current state when it mounts again. `handle.field` stays the same object throughout. A remount swaps a copy of the canvas, with its attributes and inline styles, into the same place. `lazy: false` mounts at once, anywhere on the page, and is never released by distance.
|
|
159
|
+
- **Context budget.** At most 12 kit fields hold a WebGL context at once, across all components and page helpers (browsers drop the oldest past about 16). Past the budget the live field farthest off screen is released first, and a waiting field mounts as soon as it is nearer than the farthest live one. Change it with `setFieldBudget(n)`; `getFieldBudget()` reads it.
|
|
160
|
+
- **Posters.** `poster: '/stills/hero.webp'` puts an `<img>` under the canvas (`update({ poster })` points it at another still, such as the other theme's), placed exactly where the field draws. It fades out (280 ms, none under reduced motion) when the first GPU frame lands, shows again while the field is released, and stays as the fallback without WebGL or after the field fails, until `destroy()`. `update({ poster })` swaps the still in place, for a theme change, on every component including the page helpers and `asciiVideo`. `asciiVideo` keeps its own poster.
|
|
161
|
+
- **Pausing.** As before, a field draws nothing while the page is hidden or its element is off screen.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { beam, statusOrb, setFieldBudget, setGlobalLook, subscribeGlobalLook } from 'asciify-engine/kit';
|
|
165
|
+
|
|
166
|
+
setFieldBudget(10);
|
|
167
|
+
const orb = statusOrb(el, { poster: '/stills/orb-dark.webp' });
|
|
168
|
+
|
|
169
|
+
// A header "Characters" menu: re-skins every glyph field at once.
|
|
170
|
+
setGlobalLook({ charset: ' .:-=+YCIFSA#@' });
|
|
171
|
+
setGlobalLook(null); // each field gets its own characters back
|
|
172
|
+
const off = subscribeGlobalLook(look => menu.value = look?.charset ?? '');
|
|
173
|
+
|
|
174
|
+
// Per-instance tuning from a customise panel.
|
|
175
|
+
orb.update({ ink: '--brand', look: { cellSize: 6 } });
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`setGlobalLook({ charset })` applies to every glyph field, live or released, except dot marks, fields whose own `charset` holds letters (a word or a brand, such as `wordmark`), and fields created with `globalLook: false` (use it for a locked region). An empty `charset` keeps each field's own. `getGlobalLook()` reads the current look, so `subscribeGlobalLook` pairs with React's `useSyncExternalStore`.
|
|
179
|
+
|
|
180
|
+
Each change sweeps: a field that has drawn its first frame holds that frame over itself and wipes it away left to right in 200ms (after a 40ms beat, `cubic-bezier(.23,1,.32,1)`), led by a 2px scan line, as the new characters land under it. The line is `--ascii-sweep` (default amber `#f3ba32`, at 35% with a soft glow); the held frame and the line are an `aria-hidden` canvas and span marked `data-ascii-sweep`, placed exactly over the field (style them alongside the field, for example with the same mask) and removed when the sweep ends. Under reduced motion (the visitor's, or `reducedMotion: true`), and for a field still on its poster, the characters change at once.
|
|
181
|
+
|
|
182
|
+
For your own `mountSurface` or `mountPageField` component, pass the options box as `kit` (`kit: state`) and the same box to `handle()`, so `lazy`, `poster` and `globalLook` apply and the poster is removed on `destroy()`.
|
|
183
|
+
|
|
151
184
|
## Phase 2c: page and field helpers
|
|
152
185
|
|
|
153
186
|
Whole-element fields that draw their own scene or source: page backgrounds, a wordmark, a video, the sign-in art panel and the pricing card's glow. Each lays one canvas over or under the element (inset 0, following its border radius), inks it from the theme, honours reduced motion and restores the element on `destroy()`. Without WebGL2 they return inert handles and leave the HTML alone.
|
|
@@ -168,7 +201,7 @@ const lost = fieldBackground(art, { preset: 'notFound' });
|
|
|
168
201
|
home.onclick = async () => { await lost.scatter(home); location.href = '/'; };
|
|
169
202
|
```
|
|
170
203
|
|
|
171
|
-
Options: `preset` (`'hero' | 'brand' | 'notFound'`, default `hero`), `clear` (the element to keep clear; `hero` and `brand`), `palette` (one ink or `{ light, dark }`, winning over `ink`; `brand` defaults to `var(--brand)`), `text` (`notFound`, default `404`), `ripple` (the pointer drops a soft ring every 520ms or so, default true), `label` (`notFound` defaults to its text; the grounds are hidden as decoration). Methods: `pulse(from?, strength?)` (an element, a viewport point or the centre; default 1.4), `scatter(from?)` (a promise for the re-formed numerals; other presets just pulse). The field sits under the content (`isolation: isolate`, `z-index: -1`), so give the hero its own background.
|
|
204
|
+
Options: `preset` (`'hero' | 'brand' | 'notFound'`, default `hero`), `clear` (the element to keep clear; `hero` and `brand`), `palette` (one ink or `{ light, dark }`, winning over `ink`; `brand` defaults to `var(--brand)`), `text` (`notFound`, default `404`), `ripple` (the pointer drops a soft ring every 520ms or so, default true), `label` (`notFound` defaults to its text; the grounds are hidden as decoration). Methods: `pulse(from?, strength?)` (an element, a viewport point or the centre; default 1.4), `scatter(from?)` (a promise for the re-formed numerals; other presets just pulse). The field sits under the content (`isolation: isolate`, `z-index: -1`), so give the hero its own background. Without WebGL2 its poster lies in the same place and the kit isolates the element for it too, so the page's backgrounds never cover it.
|
|
172
205
|
|
|
173
206
|
### wordmark
|
|
174
207
|
|
|
@@ -179,7 +212,9 @@ const mark = wordmark(box, { text: 'asciify' });
|
|
|
179
212
|
input.oninput = () => mark.setText(input.value);
|
|
180
213
|
```
|
|
181
214
|
|
|
182
|
-
Options: `text`, `font` (default `Georgia, "Times New Roman", serif`), `fontWeight` (default 600). Methods: `setText(text)`; getter `text`.
|
|
215
|
+
Options: `text`, `font` (default `Georgia, "Times New Roman", serif`), `fontWeight` (default 600), `artHeight` (height of the 1800 px wide canvas the word is set on, default 520; a shorter one sets the word larger in a wide element), `label` (the canvas's accessible name when the word is not the element's own text; default the word). Methods: `setText(text)`; getter `text`.
|
|
216
|
+
|
|
217
|
+
The landing's closing name is `wordmark(box, { text: 'asciify', artHeight: 480, label: 'asciify, drawn in characters', look: { charset: ' .:-=+*#%@', font: '"Geist Mono", ui-monospace, monospace', fontWeight: 400, cellSize: 9, cellAspect: 0.62, glyphScale: 0.92, contrast: 1, shade: 0.5, trail: { strength: 0.9, radius: 0.3 } } })`, and its hero is `fieldBackground(section, { preset: 'hero', ripple: false, palette: { light: '#7d7b75', dark: '#77756f' }, look: { scale: 1.25, cellSize: 12, brightness: 0.24, contrast: 1, charset: ' .:+*#', cellAspect: 0.62, glyphScale: 0.86, clear: { element: copy, padding: -24, feather: 0.6, shape: 'ellipse' } } })` (`scale` 1.6 under 760px).
|
|
183
218
|
|
|
184
219
|
### asciiVideo
|
|
185
220
|
|
|
@@ -255,7 +290,7 @@ search.oninput = () => render(filter(search.value)); // the kit re-selects row 0
|
|
|
255
290
|
cmd.select(2);
|
|
256
291
|
```
|
|
257
292
|
|
|
258
|
-
Options: `trigger`, `input` (default `input`), `rows` (default `[role="option"]`), `open` (default: open unless the panel has `hidden`), `hotkey` (⌘K / Ctrl+K, default `'k'`, `false` for none), `manage` (arrow keys, Home, End, Enter, Escape, hover, outside clicks, `aria-selected` and `aria-activedescendant`; default true), `closeOnRun` (default true), `onRun(index, row)`, `onOpenChange(open)`. Methods: `open(focus?)`, `close()`, `toggle()`, `select(i)`, `run(i?)`; getters `isOpen`, `selected`. Closed is the `hidden` attribute; `data-state` is `opening` or `closing` in flight. Give rows an `id` and a little left padding for the marker; the field lies over the panel (it ignores the pointer) and draws only in that gutter, so the marker shows on top of the row's own highlight.
|
|
293
|
+
Options: `trigger`, `input` (default `input`), `rows` (default `[role="option"]`), `open` (default: open unless the panel has `hidden`), `hotkey` (⌘K / Ctrl+K, default `'k'`, `false` for none), `manage` (arrow keys, Home, End, Enter, Escape, hover, outside clicks, `aria-selected` and `aria-activedescendant`; default true), `closeOnRun` (default true), `onRun(index, row)`, `onOpenChange(open)`. Methods: `open(focus?)`, `close()`, `toggle()`, `select(i)`, `run(i?)`; getters `isOpen`, `selected`. Closed is the `hidden` attribute; `data-state` is `opening` or `closing` in flight, and closing fades the panel's surface as its clip lands so the trigger shows through. Give rows an `id` and a little left padding for the marker; the field lies over the panel (it ignores the pointer) and draws only in that gutter, so the marker shows on top of the row's own highlight.
|
|
259
294
|
|
|
260
295
|
### slideToConfirm
|
|
261
296
|
|
|
@@ -266,7 +301,7 @@ const pay = slideToConfirm(track, { onConfirm: () => charge(), mark: track.query
|
|
|
266
301
|
charge().catch(() => pay.reset());
|
|
267
302
|
```
|
|
268
303
|
|
|
269
|
-
The track holds a `[role="slider"]` thumb (or any first child) and an optional `[data-label]`. Options: `onConfirm`, `thumb`, `label`, `mark` (where the check lands, default the track's middle), `threshold` (default 0.9), `resetAfter` (ms, default 0: stays confirmed), `color` (default `--ascii-success`). Drag, fling, a press on the track (a nudge), arrow keys, Home, End, Enter and Space all work. Methods: `confirm()`, `reset()`; getter `confirmed`. Sets `data-state` (`idle`, `dragging`, `confirmed`) and `data-confirmed` once the check is in, for your done text.
|
|
304
|
+
The track holds a `[role="slider"]` thumb (or any first child) and an optional `[data-label]`. Options: `onConfirm`, `thumb`, `label`, `mark` (where the check lands, default the track's middle), `threshold` (default 0.9), `resetAfter` (ms, default 0: stays confirmed), `color` (default `--ascii-success`). Drag, fling, a press on the track (a nudge), arrow keys, Home, End, Enter and Space all work; only the pointer that took the thumb drags it, and a pointer that cannot be captured (a scripted one) still drags. Methods: `confirm()`, `reset()`; getter `confirmed`. Sets `data-state` (`idle`, `dragging`, `confirmed`, `resetting` while the fill fades on reset) and `data-confirmed` once the check is in, for your done text.
|
|
270
305
|
|
|
271
306
|
### likeBurst
|
|
272
307
|
|
|
@@ -277,7 +312,7 @@ const like = likeBurst(button, { limit: postBody, onChange: on => api.like(post.
|
|
|
277
312
|
const save = likeBurst(saveButton, { kind: 'save' });
|
|
278
313
|
```
|
|
279
314
|
|
|
280
|
-
Options: `kind` (`'heart' | 'save'`), `icon` (default the first `svg`), `manage` (click toggles `aria-pressed`, default true), `limit` (nothing is drawn above this element's bottom), `outset` (default 40px), `onChange(pressed)`. Ink defaults to `--ascii-like` (`#e5484d`, `#ff6b70` on dark) for hearts. Methods: `burst()`, `undo()`, `toggle(pressed?)`; getter `pressed`.
|
|
315
|
+
Options: `kind` (`'heart' | 'save'`), `icon` (default the first `svg`), `manage` (click toggles `aria-pressed`, default true), `limit` (nothing is drawn above this element's bottom), `outset` (default 40px), `layer` (`'under'`, the default, draws between the button's background and its content so the count and label stay crisp over the sparks; `'over'` draws above), `onChange(pressed)`. Point `icon` at a wrapper when the button swaps a line and a filled icon: the whole wrapper hides while the characters pop and fades up as they land. Ink defaults to `--ascii-like` (`#e5484d`, `#ff6b70` on dark) for hearts. Methods: `burst()`, `undo()`, `toggle(pressed?)`; getter `pressed`. For the count, pair it with `odometer(countEl)` and call `set()` from `onChange`.
|
|
281
316
|
|
|
282
317
|
### blockChart
|
|
283
318
|
|
|
@@ -288,7 +323,7 @@ const chart = blockChart(plot, { values: visits, labels: days, onHover: (i, v, d
|
|
|
288
323
|
range.onchange = () => chart.set(nextVisits, nextDays);
|
|
289
324
|
```
|
|
290
325
|
|
|
291
|
-
Options: `values`, `labels`, `max` (default the largest plus 4%), `duration` (ms, default 520), `stagger` (default 45ms up to 7 bars, else 9), `format` (for the screen-reader table), `onHover(index, value, label)`. Methods: `set(values, labels?)`, `highlight(i | null)`; getters `active`, `values`. Adds a visually hidden table of the values and makes the plot focusable. Draw axes and the readout in HTML. Without WebGL2 the bars are plain blocks in the text colour, with the same emphasis.
|
|
326
|
+
Options: `values`, `labels`, `max` (default the largest plus 4%), `duration` (ms, default 520), `stagger` (default 45ms up to 7 bars, else 9), `format` (for the screen-reader table), `intro` (grow the bars in when first seen, default true; `false` draws them in place, as in a view that is swapped in), `onHover(index, value, label)`. Methods: `set(values, labels?)`, `highlight(i | null)`; getters `active`, `values`. Adds a visually hidden table of the values and makes the plot focusable. Draw axes and the readout in HTML. Without WebGL2 the bars are plain blocks in the text colour, with the same emphasis.
|
|
292
327
|
|
|
293
328
|
### pageDissolve
|
|
294
329
|
|
|
@@ -299,7 +334,7 @@ const page = pageDissolve(body);
|
|
|
299
334
|
tab.onclick = () => page.swap(() => render(tab.dataset.page), tab);
|
|
300
335
|
```
|
|
301
336
|
|
|
302
|
-
Options: `origin` (an element or `{ x, y }`, default top centre), `dissolve` (ms, default 140), `condense` (ms, default 160). Methods: `swap(render, origin?)` (a promise; swaps queue, and `render` may be async); getter `busy`. The kit draws the view from its DOM (text in its own font, filled and bordered boxes, media as blocks), never from image pixels. Sets `data-state` (`out`, `in`). Under reduced motion or without WebGL2, `render` runs at once.
|
|
337
|
+
Options: `origin` (an element or `{ x, y }`, default top centre), `dissolve` (ms, default 140), `condense` (ms, default 160). Methods: `swap(render, origin?)` (a promise; swaps queue, and `render` may be async); getter `busy`. The kit draws the view from its DOM (text in its own font, filled and bordered boxes, media as blocks), never from image pixels; another kit component inside the view, such as a `blockChart`, is drawn as its settled frame, and kit posters and screen-reader-only text are left out. Sets `data-state` (`out`, `in`). Under reduced motion or without WebGL2, `render` runs at once.
|
|
303
338
|
|
|
304
339
|
### swipeDeck
|
|
305
340
|
|
|
@@ -311,7 +346,7 @@ keep.onclick = () => deck.next(1);
|
|
|
311
346
|
pass.onclick = () => deck.next(-1);
|
|
312
347
|
```
|
|
313
348
|
|
|
314
|
-
Cards are `[data-card]` children stacked in the deck (absolutely, same size), each with an `<img>`. Options: `cards`, `loop` (default true), `threshold` (share of the width, default 0.4), `radius` (default the deck's border radius), `outset` (default 56px), `onSwipe(dir, index, card)`. Methods: `next(dir?)` (false at the end of a deck that does not loop); getter `index`. With the field the cards are drawn rather than shown, so put captions outside the deck; images must be same-origin or CORS-enabled to be drawn. Sets `--ascii-lean` (-1 to 1) for your buttons. Without WebGL2 one HTML card shows at a time and follows the drag.
|
|
349
|
+
Cards are `[data-card]` children stacked in the deck (absolutely, same size), each with an `<img>`. Options: `cards`, `loop` (default true), `threshold` (share of the width, default 0.4), `radius` (default the deck's border radius), `outset` (default 56px; give it the distance to your stage's edge so a fling streams across it, rows thinning before the field's edge), `leanTarget` (the element that receives `--ascii-lean`, such as the row holding your buttons; default the deck), `onSwipe(dir, index, card)`. Methods: `next(dir?)` (false at the end of a deck that does not loop); getter `index`. With the field the cards are drawn rather than shown, so put captions outside the deck; images must be same-origin or CORS-enabled to be drawn. Sets `--ascii-lean` (-1 to 1) for your buttons. Only the pointer that took the card drags it. Without WebGL2 one HTML card shows at a time and follows the drag.
|
|
315
350
|
|
|
316
351
|
### codeInput
|
|
317
352
|
|
|
@@ -321,7 +356,7 @@ A one-time-code field: each digit resolves out of scattered characters, the care
|
|
|
321
356
|
const code = codeInput(row, { length: 6, onComplete: async c => code.verify(await api.check(c)) });
|
|
322
357
|
```
|
|
323
358
|
|
|
324
|
-
The kit uses the row's `input` (or adds a transparent one, `autocomplete="one-time-code"`) and its `[data-cell]` children (or adds them), writing each digit into a `[data-digit]` span. Options: `length` (default 6), `accept` (`'digits' | 'alphanumeric'`), `cells`, `onComplete(code)`. Methods: `verify(ok)`, `clear()
|
|
359
|
+
The kit uses the row's `input` (or adds a transparent one, `autocomplete="one-time-code"`) and its `[data-cell]` children (or adds them), writing each digit into a `[data-digit]` span. Options: `length` (default 6), `accept` (`'digits' | 'alphanumeric'`), `cells`, `onComplete(code)`. Methods: `verify(ok)`, `clear()`, `fill(code)` (enters a whole code as a paste or autofill would, the characters resolving one after another, for a “From Mail” button); getters `value`, `state`. Sets `data-state` (`idle`, `error`, `success`) on the row, `data-active` on the caret's cell, `data-shown` once a digit has landed, and `aria-invalid` after a wrong code. Style the cells; the field reaches 16px past the row.
|
|
325
360
|
|
|
326
361
|
## Phase 2b: feedback, streaming and media
|
|
327
362
|
|
|
@@ -341,7 +376,7 @@ const id = toasts.push({ title: 'Thread archived', body: 'Q3 planning · Ana', a
|
|
|
341
376
|
toasts.dismiss(id);
|
|
342
377
|
```
|
|
343
378
|
|
|
344
|
-
A toast is `{ title, body?, icon?, action?, duration? }`; `icon` is a node (such as an inline SVG) or a short text. Options: `max` (default 3; the oldest dissolves when another arrives), `duration` (ms each toast stays, default 6000, 0 keeps them; pointing at or focusing the stack holds them), `render(toast, id)` (your own `<li>`; mark parts with `data-paint` = `strong`, `text`, `meta`, `tile`, `icon` or `none` to set how they gather), `label` (the list's name, default `Notifications`). Methods: `push(toast)` (returns an id), `dismiss(id)`, `clear()`; getter `toasts`. The kit adds an `<ol class="ascii-toasts">` and a `role="status"` region that announces each toast. Classes: `ascii-toast`, `-icon`, `-text`, `-title`, `-body`, `-action`, `-close`; `--ascii-toast-bg` and `--ascii-toast-ink` set the surface. The field reaches 24px past the container.
|
|
379
|
+
A toast is `{ title, body?, icon?, action?, duration? }`; `icon` is a node (such as an inline SVG) or a short text. Options: `max` (default 3; the oldest dissolves when another arrives), `duration` (ms each toast stays, default 6000, 0 keeps them; pointing at or focusing the stack holds them), `render(toast, id)` (your own `<li>`; mark parts with `data-paint` = `strong`, `text`, `meta`, `tile`, `icon` or `none` to set how they gather), `label` (the list's name, default `Notifications`), `glint` (default true: at rest a comet of characters runs round the newest toast's edge every 4.2s, outside its text; between passes the field draws nothing). Methods: `push(toast)` (returns an id), `dismiss(id)`, `clear()`; getter `toasts`. The kit adds an `<ol class="ascii-toasts">` and a `role="status"` region that announces each toast. Classes: `ascii-toast`, `-icon`, `-text`, `-title`, `-body`, `-action`, `-close`; `--ascii-toast-bg` and `--ascii-toast-ink` set the surface. The field reaches 24px past the container and lies above every toast, so the glint and gathering characters are never hidden behind one tucked away. A toast pushed before the field is drawing (still mounting, or released off screen) simply shows. The gathering characters are inked like the page, so a toast whose fill is too close to that ink to read them on (contrast under 3:1, such as an inverted dark pill on a light page) keeps its fill and shadow clear, inline and without a transition, until its text lands, then fills in with its own transition; no CSS is needed for it.
|
|
345
380
|
|
|
346
381
|
### thinking
|
|
347
382
|
|
|
@@ -353,7 +388,7 @@ await reply.stream(await ask(question)); // or an async iterable of c
|
|
|
353
388
|
regenerate.onclick = () => reply.think();
|
|
354
389
|
```
|
|
355
390
|
|
|
356
|
-
Options: `lines` (bands while thinking, default 3; the element is held at that many lines high), `label` (an element for the status), `labels` (`{ thinking, done(seconds) }`), `start` (default true; false waits for `think()`), `stagger` (ms between words, default 38), `rule` (default true). Methods: `think()`, `stream(text | chunks)` (resolves once the last word has faded in; a partial word is held until it is whole); getter `state` (`thinking`, `streaming`, `done`). Sets `aria-busy` while thinking. Ink defaults to `--ascii-ink
|
|
391
|
+
Options: `lines` (bands while thinking, default 3; the element is held at that many lines high), `label` (an element for the status), `labels` (`{ thinking, done(seconds) }`), `start` (default true; false waits for `think()`), `stagger` (ms between words, default 38), `rule` (default true), `onState(state, seconds)` (for a status you render yourself). Methods: `think()`, `stream(text | chunks)` (resolves once the last word has faded in; a partial word is held until it is whole); getter `state` (`thinking`, `streaming`, `done`). Sets `aria-busy` while thinking; with border-box sizing the held height adds the element's padding, so bottom padding makes room for the rule. Ink defaults to `--ascii-ink`, else a quiet graphite; the dots sit under the text.
|
|
357
392
|
|
|
358
393
|
### streamText
|
|
359
394
|
|
|
@@ -367,7 +402,7 @@ await out.done();
|
|
|
367
402
|
stopButton.onclick = () => out.stop();
|
|
368
403
|
```
|
|
369
404
|
|
|
370
|
-
Options: `caret` (default true: bright while writing, dim at rest), `settle` (ms the first character of a token flickers, default 60), `stagger` (ms more per character, default 18), `reasoningLines` (reasoning lines kept in view, default 2), `onState(state)`. Methods: `write(token)`, `reasoning(line)` (adds a line to a tail the kit puts just before the element, `.ascii-reasoning`: the newest lines stay in view, older ones scroll up under a fade, and it collapses once the reply is done or stopped), `done()` (resolves once the last characters land), `stop()` (lands what was written at once), `reset()` (clears the text and the reasoning); getters `state` (`idle`, `streaming`, `done`, `stopped`) and `text`. Sets `aria-busy` while streaming. The field covers the content box
|
|
405
|
+
Options: `caret` (default true: bright while writing, dim at rest), `settle` (ms the first character of a token flickers, default 60), `stagger` (ms more per character, default 18), `reasoningLines` (reasoning lines kept in view, default 2), `onState(state)`. Methods: `write(token)`, `reasoning(line)` (adds a line to a tail the kit puts just before the element, `.ascii-reasoning`: the newest lines stay in view, older ones scroll up under a fade, and it collapses once the reply is done or stopped), `done()` (resolves once the last characters land), `stop()` (lands what was written at once; stopped while only reasoning, it folds the tail away), `reset()` (clears the text and the reasoning); getters `state` (`idle`, `streaming`, `done`, `stopped`) and `text`. Sets `aria-busy` while streaming. The field covers the content box from a quarter line above to a quarter line below, in cells half a line tall and about a letter wide, so each glyph sits on its line's lowercase letters like a typed character; give the element a fixed `line-height`. New reasoning lines slide in together with the old ones. Ink defaults to the text colour.
|
|
371
406
|
|
|
372
407
|
### compare
|
|
373
408
|
|
|
@@ -378,7 +413,7 @@ const c = compare(frame, { image: '/kingfisher.webp', position: 58 }); // frame:
|
|
|
378
413
|
c.set(30, true);
|
|
379
414
|
```
|
|
380
415
|
|
|
381
|
-
Options: `image` (a URL, or an `<img>`; one already inside the frame is used in place), `position` (0–100 photo share, default 50), `label` (the slider's name), `onChange(position)`. Method: `set(position, glide?)`; getter `position`. The kit adds the photo, a divider and a `role="slider"` handle, and sets `--ascii-pos` on the frame. Images from another origin need CORS. Without WebGL2 the frame just shows the photo, with no slider.
|
|
416
|
+
Options: `image` (a URL, or an `<img>`; one already inside the frame is used in place), `position` (0–100 photo share, default 50), `label` (the slider's name), `onChange(position)`. Method: `set(position, glide?)`; getter `position`. The kit adds the photo, a divider and a `role="slider"` handle, or uses your own: an `<img>`, a `[role="slider"]` and a `[data-compare-line]` inside the frame are taken in place, keeping their classes and `aria-label`. It sets `--ascii-pos` on the frame, `aria-valuenow` and `aria-valuetext` on the handle, and `data-dragging` on the frame while a pointer drags. Images from another origin need CORS. Without WebGL2 the frame just shows the photo, with no slider.
|
|
382
417
|
|
|
383
418
|
### accordion
|
|
384
419
|
|
|
@@ -400,18 +435,18 @@ zone.setImage('/sample.webp');
|
|
|
400
435
|
saveButton.onclick = () => zone.download();
|
|
401
436
|
```
|
|
402
437
|
|
|
403
|
-
Options: `onImage({ file, src, width, height })`, `onError(message, file)`, `maxBytes` (default 10 MB), `develop` (ms, default 720), `filename` (for `download()`, default `<name>-ascii.png`)
|
|
438
|
+
Options: `onImage({ file, src, width, height })`, `onError(message, file)`, `maxBytes` (default 10 MB), `develop` (ms, default 720), `filename` (for `download()`, default `<name>-ascii.png`), `onState(state, name)`. Methods: `setImage(url | Blob)`, `clear()`, `pick()` (opens the file picker), `download(filename?)` (resolves with the PNG Blob, or null); getter `state` (`empty`, `developing`, `done`). Elements inside with `data-dropzone="pick"`, `"clear"` or `"download"` are wired to those actions. An empty element gets a title, hint and Choose file button (`.ascii-drop-empty`, hidden once an image shows); with your own content, style it from `data-state` and `data-over` on the zone. The ring is one even row of characters on every side: the canvas is trimmed to whole cells, centred, and the ring is drawn one block per cell, so no side doubles or thins at any size. Its ink defaults to `--ascii-ink`, else mid graphite. The image develops out of the dark after its levels are set, so it reads at full contrast. Give the zone a size and a background. Characters in the photo's own colours need a dark ground, so on a light zone the image is drawn on `#0b0b0a` (pass `look: { background }` for another); `download()` fills with the zone's background, or that ground.
|
|
404
439
|
|
|
405
440
|
### disintegrate
|
|
406
441
|
|
|
407
|
-
Deletes a row into characters: its text and icons become glyphs that thin out from the right and drift up and away while the row closes up. Not a handle: it returns a promise for the end.
|
|
442
|
+
Deletes a row into characters: its text and icons become shimmering glyphs that thin out from the right and drift up and away while the row closes up. Not a handle: it returns a promise for the end.
|
|
408
443
|
|
|
409
444
|
```ts
|
|
410
445
|
await disintegrate(row); // then remove the row from your data
|
|
411
446
|
await disintegrate(row, { remove: true });
|
|
412
447
|
```
|
|
413
448
|
|
|
414
|
-
Options: `duration` (ms, default 620), `collapse` (close the row's height, default true), `remove` (take the row out of the DOM at the end; default false, which leaves it hidden and collapsed for your framework to remove), `host` (the element the field is drawn over, default the row's parent, so the characters can drift past the row's box). Calling it again on the same row returns the same promise. Without WebGL2, or under reduced motion, the row closes at once.
|
|
449
|
+
Options: `duration` (ms, default 620), `collapse` (close the row's height, default true), `remove` (take the row out of the DOM at the end; default false, which leaves it hidden and collapsed for your framework to remove), `host` (the element the field is drawn over, default the row's parent, so the characters can drift past the row's box). Calling it again on the same row returns the same promise. The row stays as it is until the field can draw its first frame, so a slow mount never leaves a gap. Without WebGL2, or under reduced motion, the row closes at once.
|
|
415
450
|
|
|
416
451
|
### pullRefresh
|
|
417
452
|
|
|
@@ -423,4 +458,55 @@ refreshButton.onclick = () => pull.refresh();
|
|
|
423
458
|
pull.revealRows(feed.querySelectorAll('.just-arrived')); // or reveal any rows yourself
|
|
424
459
|
```
|
|
425
460
|
|
|
426
|
-
Options: `onRefresh` (may return a promise; the ring spins at least 600ms), `content` (the element that moves, default the first child), `threshold` (px, default 88), `messages` (`{ refreshing, done, failed }` for the live region), `reveal` (a selector for the rows `onRefresh` added: a frame after it resolves they are hidden behind the check, then resolve in from characters as the content springs back). Methods: `refresh()`, `revealRows(rows)` (each row appears as glyphs of its own text
|
|
461
|
+
Options: `onRefresh` (may return a promise; the ring spins at least 600ms), `content` (the element that moves, default the first child), `threshold` (px, default 88), `messages` (`{ refreshing, done, failed }` for the live region), `reveal` (a selector for the rows `onRefresh` added: a frame after it resolves they are hidden behind the check, then resolve in from characters as the content springs back). Methods: `refresh()`, `revealRows(rows)` (each row appears as glyphs of its own text, with its photos drawn as tone rather than flat tiles, following a row that is still opening, then hands over to the HTML over about 760ms; a row is revealed once, and under reduced motion or without WebGL2 rows just show); getter `state` (`idle`, `pulling`, `refreshing`, `done`). Sets `aria-busy` while refreshing. It engages only when the element is scrolled to the top; give it `overflow: hidden` or `auto` and keep a Refresh button for keyboard users.
|
|
462
|
+
|
|
463
|
+
## Backgrounds
|
|
464
|
+
|
|
465
|
+
Every background on asciify.org/backgrounds as one call, drawn the same way as the site's stage: the same renderers, clock, pointer smoothing, pixel ratio and graphite inks, so a preset with the same settings matches it in light and dark. The canvas sits under the element's content (`isolation: isolate`, `z-index: -1`), follows the theme, pauses off screen and in hidden tabs, and holds one still frame under reduced motion.
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { background, caustics, aurora } from 'asciify-engine/kit';
|
|
469
|
+
|
|
470
|
+
const bg = background(hero, { preset: caustics, clear: headline, speed: 0.8 });
|
|
471
|
+
bg.update({ palette: { light: '#5b4bd1', dark: '#8f80ff' }, cellSize: 10 });
|
|
472
|
+
bg.setPreset(aurora); // cross-fades, keeping your options
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Presets are separate exports, so a page pays only for the ones it imports: `background()` with one canvas preset is about 6.2 KB gzip (6.8 KB for `wave`), and each further canvas preset adds 0.2–0.9 KB (all 22 together: 13.9 KB). `drift`, `halftone`, `wordField` and `fluid` drive the GPU glyph field, so they cost about 7 KB (`fluid` 9.7 KB) plus the shared `asciify-engine/field` build, 17 KB gzip, that every field component uses (and take `lazy`, `poster`, `globalLook` and the context budget like every field component); the rest are 2D canvases and need no WebGL. To offer a menu of several, import those you list.
|
|
476
|
+
|
|
477
|
+
Options: `preset` (required), `palette` (one ink or `{ light, dark }`, winning over `color` and `ink`; default the preset's graphite for the ground), `color` (one ink for both grounds), `accent` (heads, peaks and signals for the presets that draw them; default the preset's own, or one mixed from the ink), `speed` (0–5, default the preset's tuned pace, the Speed control on the site), `cellSize` (character size in CSS px, dot spacing for `halftone`; default the preset's), `charset` (characters from empty to dense, or a named set: `standard`, `asciify`, `minimal`, `detailed`, `letters`, `technical`, `blocks`, `braille`, `dots`), `cursor` (pointer response, default on for the presets that answer it; the GPU presets also take `'trail' | 'fluid' | 'lantern'`), `clear` (an element to keep clear: the field fades out round it), `opacity` (default 1, `fluid` 0.7), `dither` (ordered dither 0–1 for the tonal presets), `text` (`wordField`'s word, default `ASCIIFY`), plus `ink`, `look` (glyph-field settings, GPU presets only) and `reducedMotion`. Methods: `setPreset(preset)`; getter `preset`. `field` is the `GlyphField` for the GPU presets and `null` for canvas ones.
|
|
478
|
+
|
|
479
|
+
`fluid` lays its cells out as the site's hero does: a whole number of 7 × 12.07 px cells (`cellSize` / 0.58 tall) stretched to fill the stage exactly, so rows keep their place down a tall page, drawn at up to 1.5 device pixels per CSS pixel. It does this through the field's `cellFit: 'fill'` (see glyph-field.md).
|
|
480
|
+
|
|
481
|
+
The canvas templates draw in the page's fonts: load Geist Mono (and JetBrains Mono for `fluid`) as the site does, and note that the page's `-webkit-font-smoothing` changes their weight on macOS just as it does on the site.
|
|
482
|
+
|
|
483
|
+
| Preset | Kind | Looks like | Size, speed, cursor |
|
|
484
|
+
| --- | --- | --- | --- |
|
|
485
|
+
| `drift` | GPU | Characters that drift slowly in broad soft lobes and clear round your content | 11, 1, trail |
|
|
486
|
+
| `halftone` | GPU | The same drifting ground as dots | 14, 1, lantern |
|
|
487
|
+
| `wordField` | GPU | Your word (`text`) as a flowing field of letters | 13, 1, trail |
|
|
488
|
+
| `fluid` | GPU | The original Asciify hero: flowing folds with a wake behind the cursor | 7, 1, wake |
|
|
489
|
+
| `caustics` | canvas | Fine pools of light drifting like water across stone | 12, 1, no |
|
|
490
|
+
| `contour` | canvas | Fine topographic lines with a slow, rolling drift | 12, 1, no |
|
|
491
|
+
| `facet` | canvas | Folded planes catching light along their edges | 12, 1, no |
|
|
492
|
+
| `etch` | canvas | Cut-grain lines following a sculpted current | 12, 1, no |
|
|
493
|
+
| `moire` | canvas | Two fine line fields overlapping into broad bands | 12, 1, no |
|
|
494
|
+
| `weave` | canvas | Interlaced threads with a subtle change in tension | 12, 1, no |
|
|
495
|
+
| `veil` | canvas | Tall folds of light across a quiet ground | 12, 1, no |
|
|
496
|
+
| `dune` | canvas | Layered sand ridges with slow light and fine grain | 12, 1, no |
|
|
497
|
+
| `wave` | canvas | An interference field with a soft vortex at the cursor | 13, 0.6, yes |
|
|
498
|
+
| `aurora` | canvas | Soft bands of light that ripple under the cursor | 13, 0.6, yes |
|
|
499
|
+
| `noise` | canvas | A slow fractal noise field that warps round the cursor | 13, 0.5, yes |
|
|
500
|
+
| `silk` | canvas | Long ribbons following a slow flow field | 13, 1, no |
|
|
501
|
+
| `rain` | canvas | Columns of falling characters with bright heads | 13, 1, no |
|
|
502
|
+
| `stars` | canvas | A sparse starfield that twinkles and leans to the cursor | 14, 1, yes |
|
|
503
|
+
| `pulse` | canvas | Rings of characters rippling out from the cursor | 14, 1, yes |
|
|
504
|
+
| `grid` | canvas | Scan bands rolling down a grid, glitching near the cursor | 12, 1, yes |
|
|
505
|
+
| `voidWell` | canvas | A gravity well spiralling characters round the cursor (`void` is reserved in JS) | 13, 1, yes |
|
|
506
|
+
| `morph` | canvas | Every cell shimmering at its own frequencies | 14, 1, no |
|
|
507
|
+
| `fire` | canvas | A cellular flame rising from the bottom edge | 13, 1, no |
|
|
508
|
+
| `dna` | canvas | Double helices scrolling past with base-pair letters | 13, 1, no |
|
|
509
|
+
| `terrain` | canvas | A side-scrolling landscape in density characters | 13, 1, no |
|
|
510
|
+
| `circuit` | canvas | Circuit traces with signals running along them | 13, 1, no |
|
|
511
|
+
|
|
512
|
+
Your own preset is an object with `id`, `name`, `description`, `cellSize`, `speed`, `cursor` and `mount(context)`; `mountCanvas(context, draw)` runs a 2D template with the same clock and pointer as the built-ins.
|
|
@@ -98,7 +98,7 @@ For GSAP, pass your installed `gsap` and `ScrollTrigger` instances through `scro
|
|
|
98
98
|
## Procedural backgrounds
|
|
99
99
|
|
|
100
100
|
```js
|
|
101
|
-
import { asciiBackground } from './wave.js'; //
|
|
101
|
+
import { asciiBackground } from './wave.js'; // 4.0–4.13: a template downloaded from asciify.org/backgrounds
|
|
102
102
|
const background = asciiBackground(host, {
|
|
103
103
|
type: 'aurora', fontSize: 14, speed: 0.8, density: 0.55,
|
|
104
104
|
});
|
|
@@ -106,4 +106,4 @@ const background = asciiBackground(host, {
|
|
|
106
106
|
background.destroy();
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
From 4.0 to 4.13, procedural backgrounds were downloadable templates at asciify.org/backgrounds, outside npm, and existing imports keep working. Newer kits ship them all as `background(host, { preset })` from `asciify-engine/kit`, Fluid included; use that for new work ([kit.md](kit.md#backgrounds), [applying-asciify.md](applying-asciify.md#9-backgrounds)).
|
|
@@ -11,7 +11,7 @@ A downloaded PNG is one output; it is not a substitute for a live component.
|
|
|
11
11
|
| Application-owned render loop | `/studio` → `createStudioRenderer` | Caller passes time, pointer input, invalidation and cleanup. |
|
|
12
12
|
| ASCII text data | Core frame/text conversion | No animation loop needed unless requested. |
|
|
13
13
|
| Full-bleed hero or brand field that must stay smooth under the cursor, or the user's word as the characters | `/field` → `mountGlyphField` (4.11+) | The field owns its wake and drift. Keep a poster and a `/studio` or core fallback for browsers without a GPU; see [glyph-field.md](glyph-field.md). |
|
|
14
|
-
|
|
|
14
|
+
| Interface component, or a background with no media | `/kit`: one call per component, `background()` with a preset | The kit owns its loop, theme ink, posters and fallback; call `destroy()` on unmount. See [applying-asciify.md](applying-asciify.md). |
|
|
15
15
|
|
|
16
16
|
Choose colors, fonts and sizes from the project's design tokens before writing settings; [design-matching.md](design-matching.md) maps the user's mood words to values.
|
|
17
17
|
|
|
@@ -28,7 +28,7 @@ Preserve the existing framework, design and chosen media. Use one renderer owner
|
|
|
28
28
|
|
|
29
29
|
## 6. Ambient background
|
|
30
30
|
|
|
31
|
-
“Build [pattern] behind [content]
|
|
31
|
+
“Build [pattern] behind [content] with `background()` from asciify-engine/kit and the [pattern] preset, imported by name. Keep the copy clear with `clear` and a frosted veil, ink from our theme tokens, pause offscreen and destroy on unmount. For a Studio render style on a background, use the downloaded studio-background.js helper and its STUDIO.md instructions.”
|
|
32
32
|
|
|
33
33
|
The website's Studio background helper renders a continuous procedural source through /studio; it supports the site's 24 styles and 12 looks. Its original template mode uses its own character/dither controls. Never imply every control applies to every renderer.
|
|
34
34
|
|
|
@@ -46,7 +46,7 @@ Use one motion owner: `animationStyle` for core, `motion` for `/hover`, or `moti
|
|
|
46
46
|
|
|
47
47
|
## 4.0 background migration
|
|
48
48
|
|
|
49
|
-
Remove imports from `asciify-engine/backgrounds` and generator imports from the root package. Download the chosen `.js` template from https://asciify.org/backgrounds and change the import to `./wave.js` (or your selected file). The template exports `asciiBackground`; keep calling `destroy()` on unmount. It handles resizing, hidden/offscreen suspension, reduced motion, and cleanup. A selected template contains only that generator; backgrounds.js is the optional full collection. Application-owned source templates are separate from npm updates. Existing installations can stay on 3.x while migrating. Text and media rendering remain supported, including text backgrounds.
|
|
49
|
+
Remove imports from `asciify-engine/backgrounds` and generator imports from the root package. Download the chosen `.js` template from https://asciify.org/backgrounds and change the import to `./wave.js` (or your selected file). The template exports `asciiBackground`; keep calling `destroy()` on unmount. It handles resizing, hidden/offscreen suspension, reduced motion, and cleanup. A selected template contains only that generator; backgrounds.js is the optional full collection. Application-owned source templates are separate from npm updates. Existing installations can stay on 3.x while migrating. Since 4.14 the kit exports every background as `background(host, { preset })` from `asciify-engine/kit`; prefer that to a downloaded template when the installed kit has it. Text and media rendering remain supported, including text backgrounds.
|
|
50
50
|
|
|
51
51
|
Use `CHARACTER_SETS` from root or `/core` for the same eight choices as the editor's `STUDIO_CHARACTER_SETS`. This data-only import does not load Studio. Keep custom characters available separately; do not import the removed `CHARSETS` object.
|
|
52
52
|
|