asciify-engine 4.16.0 → 4.18.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asciify-engine",
3
- "version": "4.16.0",
3
+ "version": "4.18.0",
4
4
  "description": "Framework-agnostic ASCII art engine. Convert images, videos, and GIFs into ASCII art rendered on canvas, or run full-bleed GPU character fields with a fluid cursor wake.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -1,17 +1,17 @@
1
1
  ---
2
2
  name: asciify-engine
3
- description: Apply Asciify the way asciify.org does - ASCII components and backgrounds that belong in a real interface, inked from the theme, clear around text, inside the page's radii and motion. Use for UI components in characters (loading, status, toasts, tabs, AI states, charts, transitions, pages), animated ASCII backgrounds, and also photo, video and GIF conversion, GPU character fields, art styles and export, hover and motion, layered typography and rendering performance.
3
+ description: Apply Asciify the way asciify.org does - ASCII components and backgrounds that belong in a real interface, inked from the theme, clear around text, inside the page's radii and motion. Use for UI components in characters (loading, status, toasts, tabs, AI states, charts, transitions, pages), animated ASCII backgrounds, and also photo, video and GIF conversion, GPU character fields, artwork looks for wallpapers, posters and prints, art styles and export, hover and motion, layered typography and rendering performance.
4
4
  metadata:
5
- tested-engine: "4.16.0"
5
+ tested-engine: "4.18.0"
6
6
  updated: "2026-10-17"
7
7
  ---
8
8
 
9
9
  # Asciify Engine
10
10
 
11
- Use the published `asciify-engine` npm package. Tested against **4.16.0**. Check the application's installed version and its public types before using newer options; a local checkout can contain unpublished changes.
11
+ Use the published `asciify-engine` npm package. Tested against **4.18.0**. Check the application's installed version and its public types before using newer options; a local checkout can contain unpublished changes.
12
12
 
13
13
  - Agent entry: https://asciify.org/skill (redirects to this Markdown document).
14
- - Components: https://asciify.org/components · Backgrounds: https://asciify.org/backgrounds
14
+ - Components: https://asciify.org/components · Looks: https://asciify.org/looks · Backgrounds: https://asciify.org/backgrounds
15
15
  - Playground: https://asciify.org/editor · API documentation: https://asciify.org/docs/api
16
16
  - Repository: https://github.com/ayangabryl/asciify-engine
17
17
 
@@ -41,6 +41,7 @@ Most requests are a feeling ("make the loading state feel alive", "a calm backgr
41
41
  | Their video in characters on the page | Kit `asciiVideo` |
42
42
  | Their photo, video or GIF converted, optionally answering the cursor | Advanced: `asciify` / `asciifyVideo` / `asciifyGif`, plus `mountHover` from `/hover` |
43
43
  | A full-bleed field with a look the kit has no option for | Advanced: `mountGlyphField` from `/field` ([glyph-field.md](references/glyph-field.md)) |
44
+ | Their photo or video as finished artwork in a named look: a wallpaper, poster, cover, print or hero artwork ("newsprint", "riso", "phosphor", "engraving", "Lego") | `mountStudio` / `exportStudio` from `/studio` with a look from [looks.md](references/looks.md) |
44
45
  | Art styles, saved looks, masks, effects, PNG/MP4/GIF export | Advanced: `mountStudio` from `/studio` ([studio-workspace.md](references/studio-workspace.md)) |
45
46
  | Several media and text layers in one artwork | Advanced: `mountStudioProject` from `/studio` ([layered-projects.md](references/layered-projects.md)) |
46
47
  | A text banner or lettering | Advanced: `asciifyText` / `renderTextToCanvas` |
@@ -69,7 +70,7 @@ const bg = background(pricing, { preset: halftone, clear: pricingCopy });
69
70
 
70
71
  The 26 presets, as export → `id` (name):
71
72
 
72
- - GPU glyph field (WebGL2, shares the context budget): `drift` → `drift` (Drift), `halftone` → `halftone` (Halftone), `wordField` → `field` (Field), `fluid` → `fluid` (Fluid).
73
+ - GPU glyph field (WebGL2, shares the context budget): `drift` → `drift` (Drift), `halftone` → `halftone` (Halftone), `swell` → `swell` (Swell), `wordField` → `field` (Field), `fluid` → `fluid` (Fluid).
73
74
  - 2D canvas (no WebGL): `caustics` → `caustics` (Caustics), `contour` → `contour` (Contour), `facet` → `facet` (Facet), `etch` → `etch` (Etch), `moire` → `moire` (Moiré), `weave` → `weave` (Weave), `veil` → `veil` (Veil), `dune` → `dune` (Dune), `wave` → `wave` (Wave), `aurora` → `aurora` (Aurora), `noise` → `noise` (Noise), `silk` → `silk` (Silk), `rain` → `rain` (Rain), `stars` → `stars` (Stars), `pulse` → `pulse` (Pulse), `grid` → `grid` (Grid), `voidWell` → `void` (Void), `morph` → `morph` (Morph), `fire` → `fire` (Fire), `dna` → `dna` (DNA), `terrain` → `terrain` (Terrain), `circuit` → `circuit` (Circuit).
74
75
 
75
76
  `voidWell` is the export for the `void` preset, because `void` is reserved in JavaScript. `wordField`'s id is `field`, the name the site uses. `fieldBackground` is separate: its `hero`, `brand` and `notFound` presets are the asciify.org hero ground, the same ground in the brand colour, and the 404.
@@ -191,7 +192,7 @@ Keep these as sibling layers. A transformed or isolated content wrapper can trap
191
192
 
192
193
  ## Studio composition and export
193
194
 
194
- For optional wallpaper/backdrops, alternate renderers, palettes, masks, saved looks, text fonts, or deterministic MP4/GIF export, read [references/studio-workspace.md](references/studio-workspace.md). `/studio` is separate from `/hover` and `AsciiOptions`; do not replace an existing hero pipeline just to add a backdrop.
195
+ For a finished artwork in a ready look (wallpaper, poster, cover, print), start from [references/looks.md](references/looks.md): 22 looks, each with the prompt, exact settings, and wallpaper/poster/loop export sizes. For optional wallpaper/backdrops, alternate renderers, palettes, masks, saved looks, text fonts, or deterministic MP4/GIF export, read [references/studio-workspace.md](references/studio-workspace.md). `/studio` is separate from `/hover` and `AsciiOptions`; do not replace an existing hero pipeline just to add a backdrop.
195
196
 
196
197
  ## GPU glyph fields
197
198
 
@@ -93,7 +93,7 @@ Text never sits on raw characters, and never in a box. Two layers do the work:
93
93
 
94
94
  | Surface | Cell size | Characters | Site reference |
95
95
  | --- | --- | --- | --- |
96
- | Page hero ground | 10–12px, broad shapes | Sparse: `' .:+*#'` in the mono face | `fieldBackground` `hero` (10px), `drift` (11px), `halftone` (14px dot spacing) |
96
+ | Page hero ground | 10–12px, broad shapes | Sparse: `' .:+*#'` in the mono face | `fieldBackground` `hero` (10px), `drift` (11px), `halftone` (14px dot spacing), `swell` (10px dot spacing) |
97
97
  | Section band or card stage | 7–10px | Fuller ramp for images (`' .:-=+*#%@'`), quiet ramp for grounds | images 7px, character fields 10px, halftone 9px |
98
98
  | Control: button, pill, tabs, code input, odometer | Leave it to the kit | The kit fits cells to the element's text | Use a monospaced label for `buttonLoading` |
99
99
 
@@ -170,7 +170,7 @@ dark.addEventListener('change', event => ground.update({ poster: still(event.mat
170
170
 
171
171
  - **Canvases are decoration.** Kit fields are `aria-hidden` and ignore the pointer. Only the canvases that are the content take a `label`: `fieldBackground` `notFound`, `asciiVideo`, `artPanel`.
172
172
  - **Real text stays in the DOM.** `decode`, `wordmark` and `streamText` keep the actual words selectable and readable; `odometer` and `pill` counts add a hidden copy; `blockChart` adds a hidden table. Do not replace text with a field.
173
- - **Status is announced.** Pass a live region to `buttonLoading` (`announce`) and `thinking` (`label`), write `statusOrb` outcomes to one yourself, and give `pullRefresh` its `messages`. `toastStack` adds its own `role="status"` region.
173
+ - **Status is announced.** Pass a live region to `buttonLoading` (`announce`), give `thinking` its `label` (it adds a hidden live copy, so the label itself needs no role), write `statusOrb` outcomes to one yourself, and give `pullRefresh` its `messages`. `toastStack` adds its own `role="status"` region.
174
174
  - **Keyboard and focus.** The kit wires keys for `tabs`, `commandPalette`, `slideToConfirm`, `compare`, `accordion`, `swipeDeck` and `codeInput`; keep them (`manage` stays true). Keep a Refresh button beside pull to refresh. A field never covers a focus ring: `beam` draws inside the border, so a 2px outline at 3px offset stays clear.
175
175
 
176
176
  ```ts
@@ -191,7 +191,7 @@ publishButton.addEventListener('click', async () => {
191
191
 
192
192
  | Surface | Presets | Notes |
193
193
  | --- | --- | --- |
194
- | Product hero, pricing, docs landing, behind copy | `drift`, `halftone`, or `fieldBackground` `hero` | GPU grounds that clear around `clear`. `halftone` is the calmest; `fieldBackground` `hero` is asciify.org's own hero, with ripples and `pulse()`. |
194
+ | Product hero, pricing, docs landing, behind copy | `drift`, `halftone`, `swell`, or `fieldBackground` `hero` | GPU grounds that clear around `clear`. `halftone` is the calmest; `swell` is the finest, a dot grid in slow waves that stays quiet in the middle even without `clear`; `fieldBackground` `hero` is asciify.org's own hero, with ripples and `pulse()`. |
195
195
  | A brand or name moment | `wordField` (id `field`), `fieldBackground` `brand` | Your word in the field; keep copy off its centre. |
196
196
  | Art-first hero with little copy | `fluid` | The original Asciify hero, 7px cells at 0.7 opacity. Not behind reading text. |
197
197
  | Quiet texture: section bands, footers, empty states, side panels | `caustics`, `contour`, `facet`, `etch`, `moire`, `weave`, `veil`, `dune`, `silk` | Canvas, no cursor, slow. The safest behind text after `halftone`. |
@@ -38,14 +38,14 @@ reply.then(() => orb.done());
38
38
  Category: AI
39
39
 
40
40
  Prompt:
41
- “Make a ⌘K command palette for [our app] with Asciify: it opens by condensing out of characters at the trigger, filters as you type, and a glyph column glides beside the selected row. Enter runs the action with a pulse.”
41
+ “Make a ⌘K command palette for [our app] with Asciify: it opens by condensing out of characters at the trigger, a soft highlight glides between rows, rows a search brings in resolve out of characters, and the AI row shimmers as a crest turns its letters to characters and back. Enter runs the action with a pulse.”
42
42
 
43
43
  ```ts
44
44
  import { commandPalette } from 'asciify-engine/kit';
45
45
 
46
- // ⌘K or the trigger opens it; arrows, Enter and Escape are wired; rows are [role="option"] with an id
46
+ // ⌘K or the trigger opens it; arrows, Enter and Escape are wired; rows are [role="option"] with an id, AI rows carry data-ai
47
47
  commandPalette(palettePanel, { trigger: paletteTrigger, onRun: (i, row) => commands[row.dataset.id!].run() });
48
- searchInput.oninput = () => renderRows(searchInput.value); // the kit re-selects the first row
48
+ searchInput.oninput = () => renderRows(searchInput.value); // the kit re-selects the first row; new rows resolve out of characters
49
49
  ```
50
50
 
51
51
  ## button-loading: Button loading
@@ -68,15 +68,16 @@ deployButton.onclick = async () => {
68
68
  Category: Components
69
69
 
70
70
  Prompt:
71
- “Make [our status badges] Asciify pills: tinted pills whose live state is a three-character indicator that breathes, a New pill with a gradient glyph sheen on hover, and a count that rolls through glyphs. Clip every character inside its pill.”
71
+ “Make [our status badges] Asciify pills: soft tinted pills with one glyph before the label, a dot in the status tone that breathes while live, turns as a half-filled spinner while syncing and blinks when a job fails; a solid ink New pill whose sparkle twinkles on hover; and a count that rolls through glyphs. Keep every character inside its pill and off the text.”
72
72
 
73
73
  ```ts
74
74
  import { pill } from 'asciify-engine/kit';
75
75
 
76
- // <span class="pill" data-tone="green" data-live><span data-dot></span>Live</span>, data-tone="new", data-count
76
+ // <span class="pill" data-tone="green" data-live><span data-dot></span><span>Live</span></span>
77
+ // <span class="pill" data-tone="new"><span data-dot></span><span>New</span></span>, <span class="pill" data-count>12</span>
77
78
  const pills = pill(document.querySelectorAll<HTMLElement>('.pill'));
78
- statusPill.dataset.tone = 'amber';
79
- pills.refresh(); // tone changes ease over 240ms
79
+ statusPill.dataset.tone = 'red';
80
+ pills.refresh(); // tone changes ease over 240ms; red blinks the dot
80
81
  pills.count(14); // only the changed digits roll
81
82
  ```
82
83
 
@@ -84,13 +85,14 @@ pills.count(14); // only the changed digits roll
84
85
  Category: Components
85
86
 
86
87
  Prompt:
87
- “Make [our segmented tabs] Asciify tabs: the active indicator is a pill of characters that streams across as a glyph trail and condenses under the new tab within 280ms. Panels crossfade; arrow keys, Home and End switch tabs.”
88
+ “Make [our segmented tabs] Asciify tabs: a crisp pill glides to the active tab, its leading end first, with a row of characters along its foot that streaks toward the new tab and settles into a tapered line. The panel's figure rolls like an odometer and its text slides in from the direction of travel; arrow keys, Home and End switch tabs.”
88
89
 
89
90
  ```ts
90
- import { tabs } from 'asciify-engine/kit';
91
+ import { odometer, tabs } from 'asciify-engine/kit';
91
92
 
92
- // role="tab" buttons with aria-controls; panels crossfade on [data-active] in your CSS
93
- const t = tabs(tablist, { duration: 280, panels: 'attribute' });
93
+ // <span class="thumb"> in the tablist, placed from --tabs-x / --tabs-y / --tabs-w / --tabs-h; role="tab" buttons
94
+ const figure = odometer(valueEl, { value: 1_280_000, format: compact });
95
+ const t = tabs(tablist, { panels: 'none', onChange: i => figure.update({ value: VALUES[i], format: FORMATS[i] }) });
94
96
  t.select(2);
95
97
  ```
96
98
 
@@ -155,16 +157,19 @@ replayButton.onclick = () => d.replay();
155
157
  Category: Components
156
158
 
157
159
  Prompt:
158
- “Build [our visitors analytics card] with bars drawn in Asciify characters: they grow in left to right, grow again on a 7d/30d toggle, and brighten under a hover readout.”
160
+ “Build [our visitors analytics card] with bars drawn in Asciify characters: crisp glyph columns with a denser cap, today lit, growing in left to right. Hover or touch scrubs the bars and rolls the headline to that day; the 7d/30d toggle glides the shared days into place while the rest grow in.”
159
161
 
160
162
  ```ts
161
- import { blockChart } from 'asciify-engine/kit';
163
+ import { blockChart, odometer } from 'asciify-engine/kit';
162
164
 
165
+ let range = '7d';
166
+ const headline = odometer(totalEl, { value: total[range] });
167
+ // plot: touch-action: pan-y; draw the axis labels in HTML below it.
163
168
  const chart = blockChart(plot, {
164
- values: visits['7d'], labels: days['7d'],
165
- onHover: (i, v, day) => { readout.textContent = i === null ? '' : `${day} · ${v!.toLocaleString()}`; },
169
+ values: visits['7d'], labels: days['7d'], look: { cellSize: 4.5 }, // small cells keep a gap between 30 bars
170
+ onHover: (i, v, day) => { label.textContent = day ?? 'Visitors'; headline.set(i === null ? total[range] : v!); },
166
171
  });
167
- rangeToggle.onchange = () => chart.set(visits[rangeToggle.value], days[rangeToggle.value]);
172
+ rangeToggle.onchange = () => { range = rangeToggle.value; chart.set(visits[range], days[range]); headline.set(total[range]); };
168
173
  ```
169
174
 
170
175
  ## route-transition: Page transition
@@ -176,8 +181,8 @@ Prompt:
176
181
  ```ts
177
182
  import { pageDissolve, blockChart } from 'asciify-engine/kit';
178
183
 
179
- const page = pageDissolve(view, { dissolve: 180, condense: 220 }); // about 400ms, from the clicked tab
180
- const chart = blockChart(chartSlot, { values: series.overview }); // at rest the chart stays in characters
184
+ const page = pageDissolve(view); // one wave from the clicked tab, about 430ms
185
+ const chart = blockChart(chartSlot, { values: series.overview, lazy: false }); // at rest the chart stays in characters
181
186
  for (const tab of navTabs) tab.onclick = () => page.swap(() => { renderView(tab.dataset.page!); chart.set(series[tab.dataset.page!]); }, tab);
182
187
  ```
183
188
 
@@ -213,7 +218,7 @@ const code = codeInput(codeRow, { length: 6, onComplete: async c => code.verify(
213
218
  Category: Components
214
219
 
215
220
  Prompt:
216
- “Make [our toasts] with Asciify: each one gathers out of characters and stacks with depth, hover fans the stack out, and dismissing breaks a toast back into drifting glyphs. Include Undo.”
221
+ “Make [our toasts] with Asciify: each one rises in and gathers out of characters, the stack layers by tone and soft shadow with no outlines, hover fans it out, and dismissing or swiping a toast away breaks it into glyphs that drift the way it went. Include Undo.”
217
222
 
218
223
  ```ts
219
224
  import { toastStack } from 'asciify-engine/kit';
@@ -243,12 +248,12 @@ reloadButton.onclick = async () => { s.reset(); renderPost(await fetchPost()); a
243
248
  Category: AI
244
249
 
245
250
  Prompt:
246
- “Show [our assistant]'s thinking state with Asciify: dot lines shimmer where the answer will appear under a Thinking… label, then the answer streams in word by word and the dots settle into a dotted rule beneath it.”
251
+ “Show [our assistant]'s thinking state with Asciify: a wave runs through the Thinking… label, each letter rolling up through glyphs and back like an odometer; when the answer starts the label rolls into Thought for 2s and the answer opens beneath it, word by word.”
247
252
 
248
253
  ```ts
249
254
  import { thinking } from 'asciify-engine/kit';
250
255
 
251
- const reply = thinking(answer, { label: statusLabel, lines: 3 }); // "Thinking…", then "Thought for 2s"
256
+ const reply = thinking(answer, { label: statusLabel }); // "Thinking…" with a rolling wave, then "Thought for 2s"
252
257
  await reply.stream(await ask(question)); // or an async iterable of chunks
253
258
  regenerate.onclick = async () => { reply.think(); await reply.stream(await ask(question)); };
254
259
  ```
@@ -308,15 +313,15 @@ resetButton.onclick = () => c.set(50, true);
308
313
  Category: Media
309
314
 
310
315
  Prompt:
311
- “Build an image accordion for [our prints]: [five photos] as panels drawn in Asciify characters. The hovered or focused panel widens and its characters give way to the full photo in 280ms, with a caption.”
316
+ “Build an image accordion for [our prints]: [five photos] as panels drawn in Asciify characters. The hovered, tapped or focused panel widens on one smooth spring while its characters cross over to the full photo, with a caption; a quick sweep across the strip doesn't open every panel on the way.”
312
317
 
313
318
  ```ts
314
319
  import { accordion } from 'asciify-engine/kit';
315
320
 
316
- // give the strip a height; panels open on hover, press or focus
321
+ // give the strip a height; panels open on hover (with intent), tap, press or focus
317
322
  accordion(printStrip, {
318
323
  images: [{ src: '/prints/stairwell.webp', title: 'Stairwell', meta: 'Silver print · $120' }, '/prints/shell.webp', '/prints/fern.webp', '/prints/dune.webp', '/prints/pier.webp'],
319
- duration: 280,
324
+ duration: 420,
320
325
  });
321
326
  ```
322
327
 
@@ -324,7 +329,7 @@ accordion(printStrip, {
324
329
  Category: Media
325
330
 
326
331
  Prompt:
327
- “Make an image dropzone with Asciify: a quiet ring of characters frames the drop area and breathes on drag-over. A dropped image develops in characters, with Replace and Download PNG.”
332
+ “Make an image dropzone with Asciify: a quiet ring of the lightest characters frames the drop area. On drag-over, packets of denser characters chase round it and a halo leans in toward the file. A dropped image opens out from the drop point in characters, with Replace and Download PNG.”
328
333
 
329
334
  ```ts
330
335
  import { dropzone } from 'asciify-engine/kit';
@@ -355,14 +360,14 @@ async function removeRow(row: HTMLElement, id: string) {
355
360
  Category: Components
356
361
 
357
362
  Prompt:
358
- “Add pull to refresh to [our feed]: pulling reveals an Asciify character orb that densifies with distance. Past the threshold it spins while refreshing, condenses into a check, and new [posts] resolve in from characters. Include a Refresh button.”
363
+ “Add pull to refresh to [our feed]: pulling draws a small, iOS-sized ring of Asciify characters round in the gap, closing with a small pop at the threshold. Let go and it opens into a spinning arc while refreshing, draws a check, and new [posts] resolve in from characters. Include a Refresh button.”
359
364
 
360
365
  ```ts
361
366
  import { pullRefresh } from 'asciify-engine/kit';
362
367
 
363
- // feedScreen: overflow auto, content in its first child; the ring spins until onRefresh resolves, then draws a check
368
+ // feedScreen: overflow auto, content in its first child; the 26px ring spins until onRefresh resolves, then draws a check
364
369
  // rows marked data-new resolve in from characters as the content springs back
365
- const pull = pullRefresh(feedScreen, { threshold: 88, reveal: '[data-new]', onRefresh: () => loadNewPosts() });
370
+ const pull = pullRefresh(feedScreen, { threshold: 64, reveal: '[data-new]', onRefresh: () => loadNewPosts() });
366
371
  refreshButton.onclick = () => void pull.refresh();
367
372
  ```
368
373
 
@@ -399,7 +404,7 @@ refreshButton.onclick = async () => mrr.set(await fetchMrr());
399
404
  Category: Pages
400
405
 
401
406
  Prompt:
402
- “Make a split sign-in page with Asciify: [image] in characters on the left that trail under the cursor, and an email field, Continue and Continue with GitHub on the right. Continue pulses the art while the link sends.”
407
+ “Make a split sign-in page with Asciify: [image] in characters on the left that trail under the cursor, and on the right a labelled email field (a filled pill with a mail icon, a focus ring on :focus-visible, the error in the label row so nothing reflows), Continue and Continue with GitHub. Continue pulses the art and shows a loading glyph while the link sends.”
403
408
 
404
409
  ```ts
405
410
  import { artPanel } from 'asciify-engine/kit';
@@ -473,29 +478,20 @@ const v = asciiVideo(player, { src: '/media/magnolia.mp4', poster: '/media/magno
473
478
  playButton.onclick = () => playButton.setAttribute('aria-label', v.toggle() ? 'Pause' : 'Play');
474
479
  ```
475
480
 
476
- ## hero-background: Hero background
481
+ ## hero-background: Background
477
482
  Category: Backgrounds
478
483
 
479
484
  Prompt:
480
- “Put a calm ASCII background behind our hero with Asciify. Characters drift toward the headline and soften under a frosted veil, ripple under the cursor, and pulse when someone presses the main button.”
485
+ “Put the Asciify Swell background behind our hero: a fine dot grid that swells in slow rolling waves, quiet in the middle. Keep it clear around the headline and let it answer the cursor.”
481
486
 
482
487
  ```ts
483
- import { fieldBackground } from 'asciify-engine/kit';
488
+ import { background, swell } from 'asciify-engine/kit';
484
489
 
485
- const bg = fieldBackground(heroSection, { preset: 'hero', clear: heroText, ripple: true });
486
- subscribeButton.onclick = () => bg.pulse(subscribeButton, 1.4);
490
+ // One of 27 presets. Import only the one you use; bg.setPreset(other) cross-fades to another.
491
+ const bg = background(heroSection, { preset: swell, clear: heroText });
487
492
  ```
488
493
 
489
- ```css
490
- /* The veil: characters blur and fade toward the words instead of stopping at a box edge. */
491
- .hero-text { position: relative; isolation: isolate; }
492
- .hero-text::before {
493
- content: ""; position: absolute; z-index: -1; inset: -24px -40px; pointer-events: none;
494
- background: color-mix(in oklab, var(--bg) 58%, transparent);
495
- backdrop-filter: blur(9px);
496
- mask-image: radial-gradient(closest-side, #000 52%, rgb(0 0 0 / .6) 74%, transparent);
497
- }
498
- ```
494
+ Any preset from [the kit reference](kit.md#backgrounds) fits the same call: `swell`, `drift`, `halftone`, `wordField` and `fluid` run on the GPU glyph field; the rest are 2D canvases. For asciify.org's own hero ground, with the frosted veil and `pulse()`, use `fieldBackground(heroSection, { preset: 'hero', clear: heroText })`.
499
495
 
500
496
  ## Advanced: raw field
501
497
 
@@ -34,8 +34,9 @@ Size the canvas with CSS (for example `position:absolute; inset:0; width:100%; h
34
34
  | Option | Default | Notes |
35
35
  | --- | --- | --- |
36
36
  | `source` | none | URL (`.mp4`, `.webm`, `.mov` load as muted, looping, inline video), `HTMLImageElement`, `HTMLVideoElement`, `HTMLCanvasElement` or `ImageBitmap`. Cross-origin media needs CORS. |
37
- | `scene` | `'flow'` | Generated motion when there is no source: `'flow'` (domain-warped noise), `'drift'` (a few broad lobes, made for halftone, 4.12), `'orb'` (lit sphere) or `'none'` (blank, wake only). |
37
+ | `scene` | `'flow'` | Generated motion when there is no source: `'flow'` (domain-warped noise), `'drift'` (a few broad lobes, made for halftone, 4.12), `'swell'` (three crossing sine swells rolling diagonally, made for a fine dot grid), `'orb'` (lit sphere) or `'none'` (blank, wake only). |
38
38
  | `mark`, `dotScale` | `'glyph'`, `0.9` | `'dot'` draws a halftone: one circle per cell whose area follows tone, antialiased at any pixel ratio. `dotScale` is the largest dot as a fraction of the cell; about 0.6 keeps a calm, open grid. (4.12) |
39
+ | `mark` (Studio styles) | | `FIELD_MARKS` also holds the Studio's tile and print styles, drawn per cell in the composite pass from the cell's tone and colour (one shader branch each, no extra pass or texture). Tiles, which fade with tone in `color: 'ink'` and stay solid in `'source'`: `'pixel'` (filled cell), `'mosaic'` (gapped tile), `'lego'` (gapped brick lit from above, with a ringed stud), `'voxel'` (isometric cube, faces at 1, 0.72 and 0.42), `'disco'` (gapped tile, lit upper facet, shaded lower), `'hex'` (flat-topped hexagon), `'led'` (round lamp, soft halo, glint). `'dither'` lights a 4x4 Bayer screen of about five dots a cell. `'cmyk'` separates the cell's hue into offset cyan, magenta and yellow dots, with the neutral share as a key dot in the ink's own lightness. Print marks, whose size carries the tone: `'stipple'` (four jittered dots a cell), `'engraving'` (parallel cuts at -35°, running on across cells), `'crosshatch'` (two more directions join in darker tones), `'woodblock'` (broad carved bands in five steps, edges wavering), `'risograph'` (main ink and a coral second ink, offset), `'pointillism'` (four small strokes, their shade varied). Every mark but `'dot'` leaves blank the cells the `charset` leaves blank (its first glyph), so keep a ramp that starts with a space. Cells near square suit them best. |
39
40
  | `scale` | `1` | Feature size of generated scenes; higher is finer. Use about 1.8 on phones, where the field is tall. (4.12) |
40
41
  | `clear` | `false` | Keeps the field quiet around content so text stays readable: an element, `{ element, padding, feather, shape }` (followed through layout changes), or a zone in field fractions. (4.12) |
41
42
  | `charset` | `' .,:-=+*#%@'` | Ordered from empty to dense. Graphemes, so emoji, braille and block shading each take one cell. Up to 256. |