asciify-engine 4.9.0 → 4.11.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.
Files changed (40) hide show
  1. package/README.md +30 -0
  2. package/dist/animation-presets-DhswE0D_.d.cts +105 -0
  3. package/dist/animation-presets-ZujFF3i8.d.ts +105 -0
  4. package/dist/core.cjs +1 -1
  5. package/dist/core.d.cts +4 -4
  6. package/dist/core.d.ts +4 -4
  7. package/dist/core.js +1 -1
  8. package/dist/field.cjs +1 -0
  9. package/dist/field.d.cts +183 -0
  10. package/dist/field.d.ts +183 -0
  11. package/dist/field.js +1 -0
  12. package/dist/{hover-catalog-BeIkNwhH.d.cts → hover-catalog-D85jh6dR.d.cts} +40 -1
  13. package/dist/{hover-catalog-BeIkNwhH.d.ts → hover-catalog-D85jh6dR.d.ts} +40 -1
  14. package/dist/hover.cjs +1 -1
  15. package/dist/hover.d.cts +4 -3
  16. package/dist/hover.d.ts +4 -3
  17. package/dist/hover.js +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.cts +3 -3
  20. package/dist/index.d.ts +3 -3
  21. package/dist/index.js +1 -1
  22. package/dist/{model-ChcdXIzJ.d.ts → model-BVeBDUa9.d.ts} +1 -1
  23. package/dist/{model-fv_acoD_.d.cts → model-DQNOpbbG.d.cts} +1 -1
  24. package/dist/studio.cjs +1 -1
  25. package/dist/studio.d.cts +4 -3
  26. package/dist/studio.d.ts +4 -3
  27. package/dist/studio.js +1 -1
  28. package/dist/{types-C05AfrjT.d.ts → types-CM4452nD.d.ts} +1 -1
  29. package/dist/{types-D38BMBRB.d.cts → types-FkqhgDYx.d.cts} +1 -1
  30. package/examples/glyph-field.ts +44 -0
  31. package/examples/living-image.ts +15 -0
  32. package/package.json +20 -4
  33. package/skills/asciify-engine/SKILL.md +53 -14
  34. package/skills/asciify-engine/agents/openai.yaml +1 -1
  35. package/skills/asciify-engine/references/design-matching.md +179 -0
  36. package/skills/asciify-engine/references/glyph-field.md +75 -0
  37. package/skills/asciify-engine/references/project-integration.md +5 -1
  38. package/skills/asciify-engine/references/studio-effects.md +29 -0
  39. package/skills/asciify-engine/references/studio-workspace.md +5 -1
  40. package/skills/asciify-engine/references/upgrading.md +4 -0
@@ -1,4 +1,4 @@
1
- import { A as AmbientMotion } from './hover-catalog-BeIkNwhH.cjs';
1
+ import { A as AmbientMotion } from './hover-catalog-D85jh6dR.cjs';
2
2
 
3
3
  type ColorMode = 'grayscale' | 'fullcolor' | 'matrix' | 'accent';
4
4
  type RenderMode = 'ascii' | 'dots';
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A full-bleed hero field in your brand's letters, with a poster until the GPU frame is ready
3
+ * and the canvas renderer as the fallback when WebGL2 is unavailable.
4
+ */
5
+ import { isGlyphFieldSupported, mountGlyphField, type GlyphField } from 'asciify-engine/field';
6
+
7
+ export function mountBrandField(host: HTMLElement, word: string, video: string, poster: string): () => void {
8
+ const canvas = document.createElement('canvas');
9
+ canvas.style.cssText = 'position:absolute;inset:0;width:100%;height:100%';
10
+ const still = new Image();
11
+ still.src = poster;
12
+ still.alt = '';
13
+ still.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;object-fit:cover;transition:opacity .3s';
14
+ host.append(canvas, still);
15
+
16
+ let field: GlyphField | null = null;
17
+ if (isGlyphFieldSupported()) {
18
+ try {
19
+ field = mountGlyphField(canvas, {
20
+ source: video,
21
+ charset: ' .,:-+' + word.toUpperCase(),
22
+ cellSize: 12,
23
+ cellAspect: 0.8,
24
+ font: 'Inter, Arial, sans-serif',
25
+ color: 'ink',
26
+ ink: '#f3ba32',
27
+ shade: 1,
28
+ vignette: 0.35,
29
+ fluid: { radius: 0.08, refraction: 0.03, glow: 0.6 },
30
+ interactionTarget: host,
31
+ label: `${word}, drawn in characters`,
32
+ });
33
+ void field.ready.then(() => { still.style.opacity = '0'; });
34
+ } catch {
35
+ field = null;
36
+ }
37
+ }
38
+
39
+ return () => {
40
+ field?.destroy();
41
+ canvas.remove();
42
+ still.remove();
43
+ };
44
+ }
@@ -0,0 +1,15 @@
1
+ import { STUDIO_ANIMATION_PRESETS } from 'asciify-engine/studio';
2
+ import { createComposition } from './studio-composition.js';
3
+
4
+ /** A live still-image composition, with settings persistence and independent export. */
5
+ export async function createLivingImage(canvas: HTMLCanvasElement, source: File | string, signal?: AbortSignal) {
6
+ const composition=await createComposition(canvas,source,signal);
7
+ function applyAnimation(id: string) {
8
+ const preset=STUDIO_ANIMATION_PRESETS.find(item=>item.id===id);
9
+ if(!preset)throw new Error(`Unknown animation preset: ${id}`);
10
+ // Partial group updates preserve edgeSafe, the art style, source and finish.
11
+ composition.update({motion:preset.motion,hover:preset.hover});
12
+ }
13
+ applyAnimation('quiet-light');
14
+ return {...composition,applyAnimation};
15
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "asciify-engine",
3
- "version": "4.9.0",
4
- "description": "Framework-agnostic ASCII art engine. Convert images, videos, and GIFs into ASCII art rendered on canvas.",
3
+ "version": "4.11.0",
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",
7
7
  "module": "./dist/index.js",
@@ -46,6 +46,16 @@
46
46
  "types": "./dist/studio.d.cts",
47
47
  "default": "./dist/studio.cjs"
48
48
  }
49
+ },
50
+ "./field": {
51
+ "import": {
52
+ "types": "./dist/field.d.ts",
53
+ "default": "./dist/field.js"
54
+ },
55
+ "require": {
56
+ "types": "./dist/field.d.cts",
57
+ "default": "./dist/field.cjs"
58
+ }
49
59
  }
50
60
  },
51
61
  "files": [
@@ -59,7 +69,9 @@
59
69
  "examples/layered-composition.ts",
60
70
  "examples/print-composition.ts",
61
71
  "THIRD_PARTY.md",
62
- "licenses"
72
+ "licenses",
73
+ "examples/living-image.ts",
74
+ "examples/glyph-field.ts"
63
75
  ],
64
76
  "scripts": {
65
77
  "build": "tsup",
@@ -136,7 +148,11 @@
136
148
  "ascii-hero",
137
149
  "landing-page-background",
138
150
  "website-background",
139
- "ascii-particles"
151
+ "ascii-particles",
152
+ "webgl",
153
+ "webgl2",
154
+ "gpu",
155
+ "ascii-shader"
140
156
  ],
141
157
  "author": "ayangabryl",
142
158
  "license": "MIT",
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: asciify-engine
3
- description: Build and tune browser ASCII images, video, GIFs, text, and interactive heroes with asciify-engine. Use for media conversion, character and color choices, hover integration, layered HTML typography, scroll-synced video, and rendering performance in apps using this package.
3
+ description: Build and tune browser ASCII art with asciify-engine so it fits the user's request, preferences and the project's design. Use for photo, video and GIF conversion, interactive heroes, GPU character fields with the user's own words, ambient backgrounds, text banners, art styles and export, hover and motion choices, layered HTML typography, and rendering performance.
4
4
  metadata:
5
- tested-engine: "4.9.0"
6
- updated: "2026-10-07"
5
+ tested-engine: "4.11.0"
6
+ updated: "2026-10-09"
7
7
  ---
8
8
 
9
9
  # Asciify Engine
10
10
 
11
- Use the published `asciify-engine` npm package for browser canvas rendering. Tested against **4.9.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 for browser canvas rendering. Tested against **4.11.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
14
  - Playground: https://asciify.org/editor
@@ -19,7 +19,29 @@ Use the published `asciify-engine` npm package for browser canvas rendering. Tes
19
19
  npm install asciify-engine@latest
20
20
  ```
21
21
 
22
- Version 4.0 exports `asciify-engine` and `asciify-engine/core` for media rendering. Procedural background templates are downloaded from asciify.org/backgrounds and are not npm exports. Root imports remain supported. GIF decoding loads `gifuct-js` when a GIF API is called. Enhanced website hovers, living-image motion and fine dither are available from the optional `asciify-engine/hover` entry; root and `/core` do not load it.
22
+ Entries: `asciify-engine` and `/core` for media and text conversion; `/hover` for the website hovers and living-image motion; `/studio` for art styles, looks and export; `/field` for GPU character fields. Each optional entry loads only when imported. Procedural background templates are downloaded from asciify.org/backgrounds and are not npm exports. GIF decoding loads `gifuct-js` when a GIF API is called.
23
+
24
+ ## Start here: the request, then the design
25
+
26
+ Most requests are a feeling ("make the hero feel alive", "retro", "subtle behind the pricing", "like unseen.co, with our name") rather than an API. Before writing code:
27
+
28
+ 1. **Read the request.** Subject (their media, a word, or nothing), placement (hero, background behind text, card, banner), mood words, interaction, and any reference site.
29
+ 2. **Read the design.** Open the project's tokens (CSS custom properties, Tailwind theme, brand file): page background, text colors, the one accent, mono and display fonts, light/dark themes, motion durations and easing, reduced-motion handling.
30
+ 3. **Pick the tool** from the table below, then **derive every color, font and size from the tokens**. Never add a new brand color, a black canvas on a light site, or a white hover glow on a colored artwork.
31
+ 4. **Decide living or still.** A still photo can be made to feel alive (field `drift`, studio `motion`), but some users want it still. Follow their words; keep art behind text, in grids and in data views still; reduced motion always gets a still. See [living or still](references/design-matching.md#living-or-still).
32
+ 5. **Say what you chose in one line** and offer one alternative. Ask only when the media is missing or motion would be new to a site that has none.
33
+
34
+ [references/design-matching.md](references/design-matching.md) maps mood words to settings, explains light themes, contrast and fonts, and gives tested recipes for the common requests. Read it whenever the user describes a look rather than an option.
35
+
36
+ | The user wants | Use |
37
+ | --- | --- |
38
+ | Their photo, video or GIF as ASCII on the page, optionally answering the cursor | `asciify` / `asciifyVideo` / `asciifyGif`, plus `mountHover` from `/hover` for the website hovers |
39
+ | A full-bleed hero or brand field that stays smooth under the cursor, or their own word as the characters | `mountGlyphField` from `/field` ([glyph-field.md](references/glyph-field.md)); `hover: 'trail'` for the classic Asciify Trail on pure characters, `'fluid'` for bold and liquid, `'lantern'` for a subtle light |
40
+ | Art styles, saved looks, backdrops, masks, effects, PNG/MP4/GIF export | `mountStudio` from `/studio` ([studio-workspace.md](references/studio-workspace.md)) |
41
+ | Several media and text layers in one artwork | `mountStudioProject` from `/studio` ([layered-projects.md](references/layered-projects.md)) |
42
+ | An ambient background with no media | A template from asciify.org/backgrounds, or a `/field` generated scene |
43
+ | A text banner or lettering | `asciifyText` / `renderTextToCanvas` |
44
+ | Headline text visible through the art | Layered HTML ([layered-hero.md](references/layered-hero.md)) |
23
45
 
24
46
  ## Install the agent skill
25
47
 
@@ -86,15 +108,15 @@ const letters = [...new Set('ASCIIFY'.toUpperCase().replace(/[^A-Z]/g, ''))].joi
86
108
  const charset = ` .:-=+${letters}#%@`;
87
109
  ```
88
110
 
89
- Preview it: letter glyph densities differ, so arbitrary words are not automatically an evenly ordered ramp. Keep the leading space and the light/dense symbols. Use the curated `CHARACTER_SETS.standard.chars`, `detailed.chars`, `blocks.chars`, or `braille.chars` from `/core` when appropriate.
111
+ Preview it: letter glyph densities differ, so arbitrary words are not automatically an evenly ordered ramp. When the user wants the artwork drawn in their name, `/field` handles it better: put the word after a quiet ramp (`' .,:-+' + word`), use one ink with `shade: 1`, and let brightness carry the image (see [design-matching.md](references/design-matching.md)). Keep the leading space and the light/dense symbols. Use the curated `CHARACTER_SETS.standard.chars`, `detailed.chars`, `blocks.chars`, or `braille.chars` from `/core` when appropriate.
90
112
 
91
113
  ## Engine effects versus studio effects
92
114
 
93
- The public npm `HoverEffect` IDs in 1.2.0 are `spotlight`, `magnify`, `repel`, `glow`, `colorShift`, `attract`, `shatter`, `trail`, and `glitchText`. Those remain valid APIs, but they are **not identical to the current website's enhanced interactions**.
115
+ The core `HoverEffect` IDs are `spotlight`, `magnify`, `repel`, `glow`, `colorShift`, `attract`, `shatter`, `trail`, and `glitchText`. Those remain valid APIs, but they are **not identical to the current website's enhanced interactions**.
94
116
 
95
- The website offers **Trail, Water, Contour, Dissolve, Silk, Vortex, Magnetic Pull, Scatter, Emboss, Elastic, Rake, and Off** through an optional studio surface module. Its ten still-image motions—Caustics, Slow Current, Reveal & Reform, Sheen, Tidal Rings, Living Grain, Parallax, Woven Flow, Print Shift, Contour Light—and fine-dither treatments ship in the same `asciify-engine/hover` module. Use its typed options rather than converting display names into engine `hoverEffect` or `animationStyle` values.
117
+ The website offers **Trail, Water, Contour, Dissolve, Silk, Vortex, Magnetic Pull, Scatter, Emboss, Elastic, Rake, Lens wake, Smudge, Ripple rings, and Off** through an optional studio surface module. Its fourteen still-image motions—Caustics, Slow Current, Reveal & Reform, Sheen, Tidal Rings, Living Grain, Parallax, Woven Flow, Print Shift, Contour Light, Relight, Shimmer, Breeze, Unfold—and fine-dither treatments ship in the same `asciify-engine/hover` module. Use its typed options rather than converting display names into engine `hoverEffect` or `animationStyle` values.
96
118
 
97
- To reproduce those interactions, read [references/studio-effects.md](references/studio-effects.md). Import `mountHover` from `asciify-engine/hover` (1.2.0+); no separate download is needed. Keep engine `hoverStrength: 0` and `animationStyle: 'none'` when using it, to avoid applying two effects. Trail is the site's default. Prefer a stationary-grid effect such as Trail or Dissolve when the user wants a wake without spatial displacement.
119
+ To reproduce those interactions, read [references/studio-effects.md](references/studio-effects.md). Import `mountHover` from `asciify-engine/hover`; no separate download is needed. Keep engine `hoverStrength: 0` and `animationStyle: 'none'` when using it, to avoid applying two effects. Trail is the site's default. Prefer a stationary-grid effect such as Trail or Dissolve when the user wants a wake without spatial displacement.
98
120
 
99
121
  For built-in hover alone, use a supported `options.hoverEffect` and a restrained nonzero `hoverStrength`; validate its behavior instead of promising that it matches the website.
100
122
 
@@ -115,7 +137,8 @@ Choose the more conventional arrangement—artwork behind all text—when it imp
115
137
  ## Performance and lifecycle
116
138
 
117
139
  - Reduce processing dimensions and cell count before dropping source playback FPS. Do not default to 60 fps on a 24 fps video.
118
- - Start video at 24–30 fps with a 720–960px processing cap. More processing pixels do not create detail absent from the source. Version 1.2.0 includes the renderer fixes previously applied by the website; the enhanced surface module is included through the optional `/hover` import.
140
+ - Start video at 24–30 fps with a 720–960px processing cap. More processing pixels do not create detail absent from the source.
141
+ - When the user asks for a smooth, high-frame-rate hero, prefer `/field` over a canvas pipeline, and keep the canvas pipeline as its fallback.
119
142
  - Keep stable canvas dimensions; resize on layout changes, not on every animation frame.
120
143
  - Avoid remounting the renderer on pointer movement or each React render.
121
144
  - Pause media offscreen and when the document is hidden. Reduced-motion users should get a still; do not merely hide a playing video with CSS.
@@ -128,16 +151,20 @@ Choose the more conventional arrangement—artwork behind all text—when it imp
128
151
 
129
152
  Check the actual implementation: readable subject and text, no stretching or unintended empty margins, working pointer and keyboard controls, mobile crop, reduced motion, autoplay failure, and cleanup after navigation. Observe several seconds of motion and an entire loop boundary. State the measured device/settings if reporting FPS; screenshots and recordings alone do not establish performance.
130
153
 
131
- Do not import nonexistent helpers such as `createRecorder` or `recordAndDownload` from 1.2.0. For deterministic composition export in 1.4.0+, use `/studio` as described below. For recording an existing interactive canvas, use a browser `MediaRecorder` with a supported `canvas.captureStream()` format and clean up its tracks; for snapshots, inspect the published `captureSnapshot` / `snapshotAndDownload` types.
154
+ Do not import nonexistent helpers such as `createRecorder` or `recordAndDownload`. For deterministic composition export, use `/studio` as described below. For recording an existing interactive canvas, use a browser `MediaRecorder` with a supported `canvas.captureStream()` format and clean up its tracks; for snapshots, inspect the published `captureSnapshot` / `snapshotAndDownload` types.
132
155
 
133
156
 
134
157
  ## Hero finish
135
158
 
136
- Version 1.3.0 adds text-shaped charcoal cutouts (`textMask`), peripheral scanline/fringe control (`edgeEffect`), fixed accent ink, Prism edge softness (`edgeSoftness`), and fixed-grid fluid Trail. These additions ship in **npm 1.3.0**. Upgrade older applications before using them. See the [complete API, layering example, lifecycle, and fallback limits](references/hero-finish.md).
159
+ `/hover` supports text-shaped charcoal cutouts (`textMask`), peripheral scanline/fringe control (`edgeEffect`), fixed accent ink, Prism edge softness (`edgeSoftness`), and fixed-grid fluid Trail. See the [complete API, layering example, lifecycle, and fallback limits](references/hero-finish.md).
137
160
 
138
161
  ## Studio composition and export
139
162
 
140
- 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). The new `/studio` API is separate from `/hover` and `AsciiOptions`; do not replace an existing hero pipeline just to add a backdrop. Use version 1.4.0 or newer for these APIs after verifying the installed package.
163
+ 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.
164
+
165
+ ## GPU glyph fields
166
+
167
+ For a full-bleed hero, section background or brand field that must stay smooth under the cursor, use `mountGlyphField` from `asciify-engine/field` and read [references/glyph-field.md](references/glyph-field.md). It draws glyph ramps (including words and braille) from images, video or generated motion on WebGL2 with a fluid wake that keeps each character's own color. It does not replace `/studio` for the 24 render styles, dither, print styles, effects or export. Check `isGlyphFieldSupported()` (false without a GPU), always provide a poster and a canvas fallback, and call `destroy()` on unmount.
141
168
 
142
169
  ## Current catalogs
143
170
 
@@ -146,7 +173,11 @@ For multiple media/text layers in one live component, use 4.8+ `mountStudioProje
146
173
  Legacy `CHARSETS`, `ART_STYLE_PRESETS`, `CHARSET_SEQUENCES`, `LIVING_STYLE_PRESETS` and their key types are removed. Do not import them or generate old names such as Katakana, Binary, Waves or Smoke. Use the 24 `STUDIO_STYLES` for render-style selectors and `/studio` rendering. For ASCII-only character ramps use `CHARACTER_SETS` from `/core` or `STUDIO_CHARACTER_SETS` from `/studio`; custom Unicode and user-provided frame arrays remain supported. Core `artStyle` only accepts `classic`; use explicit options instead of preset shortcuts. Despite the requested 4.1.0 version number, migrating these removed APIs is a breaking change.
147
174
 
148
175
 
149
- ## Fine dither and creative controls (4.2)
176
+ ## Version notes
177
+
178
+ Read these when the installed version is older than the tested one, or when a user names a specific feature.
179
+
180
+ ### Fine dither and creative controls (4.2)
150
181
 
151
182
  Studio adds `hex`, `led`, and `cmyk` render styles, directional/radial dither patterns, additional palettes, luminance mapping, RGB split, sharpening, and adjustable still-image motion strength. Read [studio-workspace.md](references/studio-workspace.md#fine-detail-and-controls-42) for exact options, density budgets and preview/export consistency. These are local Canvas/WebGL effects, not AI generation or a claim of parity with every competing catalog.
152
183
 
@@ -157,3 +188,11 @@ From 4.6, Studio adds `color.curves` (RGB, red, green, blue) and optional edge-p
157
188
  From 4.7, Studio supports up to eight ordered source warps: Twirl, Pinch, Spherize, Ripple, Zigzag, Shear, Smudge, Polar and Fragment. Build controls from `STUDIO_WARPS`, retain array order, and read [source shaping](references/source-shaping.md) for bounds, alpha/edge policy and cached-map performance. These shape the source; use `motion.type` to bring a still image to life and `hover.effect` for pointer interaction. Keep them independent, with an empty warp stack by default.
158
189
 
159
190
  From 4.9, optional Studio adds Stipple, Engraving, Crosshatch, Woodblock, Risograph and Pointillism with a shared `print` settings group. Read [print and illustration](references/print-styles.md) for exact controls, light-ink versus dark-ink polarity, still-motion integration and dense-render limits. These are genuine additional render paths; do not substitute dither presets for their IDs.
191
+
192
+ ### Lively images and interaction starting points (4.10)
193
+
194
+ `STUDIO_ANIMATION_PRESETS` provides six editable motion/hover pairings through `/studio` and `/hover`. Apply `motion` and `hover` to an existing Studio player to keep the source and art style; do not treat the pairings as additional art styles. Read [studio-effects.md](references/studio-effects.md#lively-images-410) for exact new IDs, loop limits and the live component example.
195
+
196
+ ### GPU glyph fields (4.11)
197
+
198
+ `asciify-engine/field` adds `mountGlyphField`, `isGlyphFieldSupported` and `glyphFieldTier`. Older installs do not have this entry; upgrade before using it.
@@ -1,7 +1,7 @@
1
1
  interface:
2
2
  display_name: "Asciify Engine"
3
3
  short_description: "Build fast ASCII media effects"
4
- brand_color: "#B3ED82"
4
+ brand_color: "#F3BA32"
5
5
  default_prompt: "Use $asciify-engine to build interactive ASCII media or layered compositions, keeping HTML controls readable, rendering budgets explicit, and media lifecycles isolated."
6
6
 
7
7
  policy:
@@ -0,0 +1,179 @@
1
+ # Match the request, the user and the design
2
+
3
+ An Asciify integration is good when it looks like it was made for that page: the user's words decide the effect, the project's design decides the look, and the measured result decides the budget. Work through these steps before writing code.
4
+
5
+ ## 1. Read the request
6
+
7
+ Note, in the user's own terms:
8
+
9
+ - **Subject.** What is drawn: their photo or video, a product shot, a logo or word, or no media at all (an ambient background).
10
+ - **Placement and role.** A hero, a section background behind text, a card or thumbnail, a loading state, a text banner, or an editor feature.
11
+ - **Mood words.** Calm, bold, retro, editorial, techy, playful, premium, minimal. Map them with the table in step 4.
12
+ - **Interaction.** Whether it should answer the cursor, move by itself, play a video, scroll-scrub, or stay still.
13
+ - **References.** "Like unseen.co", "like a terminal", "like a newspaper print". Name what you take from the reference (technique, density, palette) and what you keep from the user's brand.
14
+
15
+ Ask only for what you cannot infer, and ask once:
16
+
17
+ - The media, when the result depends on their photo or video and none is in the project.
18
+ - Whether motion is welcome, when the site has no motion at all and the request did not mention it.
19
+
20
+ Otherwise decide, and state your choices in one line ("phosphor ink from `--accent`, 12px cells, field with a fluid wake, still under reduced motion").
21
+
22
+ ## 2. Read the project's design
23
+
24
+ Before choosing colors or sizes, open the project's source of truth:
25
+
26
+ - **Tokens.** CSS custom properties (`:root`, `[data-theme]`), the Tailwind theme, a `brand.json` or design-tokens file. Collect the page background, surface, text colors, the one accent, and the mono and display font stacks.
27
+ - **Theme.** Dark, light, or both (`prefers-color-scheme`, a theme attribute). If both exist, the art must work in both.
28
+ - **Motion language.** Existing durations, easing variables and `prefers-reduced-motion` handling. Asciify motion should sit inside it.
29
+ - **Density of the layout.** A sparse editorial page wants larger, quieter cells; a dense product UI wants the art contained in one place.
30
+ - **Components.** Reuse the project's poster, skeleton, error and button patterns around the canvas instead of inventing new ones.
31
+
32
+ Never introduce a new brand color, a black canvas on a light site, or a second accent. If the brand has no accent, use its text color for ink.
33
+
34
+ ## 3. Pick the tool
35
+
36
+ | The user wants | Use | Notes |
37
+ | --- | --- | --- |
38
+ | A photo, video or GIF converted on their page, with optional hover | `asciify`, `asciifyVideo`, `asciifyGif` (+ `mountHover` from `/hover`) | Lightest path. Disable core hover and motion when `/hover` owns them. |
39
+ | A full-bleed hero or brand field that must feel smooth under the cursor, their name or word as the characters, an Unseen-style look | `mountGlyphField` from `/field` | WebGL2 on a GPU, 120 fps class. Glyph ramps only. Keep a poster and a fallback. See [glyph-field.md](glyph-field.md). |
40
+ | Art styles (halftone, dither, braille, print, mosaic, LED…), saved looks, backdrops, masks, effects, PNG/MP4/GIF export | `mountStudio` from `/studio` | Studio owns motion, hover and finish. Start from `studioLook(id)` when one of the 12 looks matches the mood. |
41
+ | Several media and text layers in one artwork | `mountStudioProject` from `/studio` | One loop and budget for up to eight layers. See [layered-projects.md](layered-projects.md). |
42
+ | An ambient animated background with no media (rain, stars, waves, aurora…) | A background template from asciify.org/backgrounds | Downloaded files, not npm exports. Or a `/field` generated scene (`flow`, `orb`) for a GPU field. |
43
+ | A text banner or lettering | `asciifyText` / `renderTextToCanvas` | Plain text output works anywhere; canvas for color. |
44
+ | Headline text visible through the art | Layered HTML, see [layered-hero.md](layered-hero.md) | Real text stays in the DOM. |
45
+
46
+ ## 4. Turn mood words into settings
47
+
48
+ | Mood | Characters | Color | Size and density | Motion and hover |
49
+ | --- | --- | --- | --- | --- |
50
+ | Calm, premium, minimal | Round ramp `' .:*oO0@'` or `' .:-=+*'` | One ink from the brand, or source color with a soft `backdrop` 0.2–0.4 | 8–12px cells; vignette 0.2–0.4 | Field `hover: 'lantern'` or studio Trail; slow drift (0.1–0.3) or none |
51
+ | Bold, poster, loud | Dense ramp `' .:-=+*#%@'`, blocks | High contrast (1.2–1.5), brand ink on page background | 12–20px cells | Field `hover: 'fluid'` with `glow` 0.7–0.9, or studio Ripple / Elastic |
52
+ | Retro terminal, hacker | Mono ramp, `technical` set | `matrix` (core) or `terminal` look (studio), or the brand accent as phosphor | 7–10px | Scanlines, CRT, character bloom (studio effects); Trail |
53
+ | Editorial, print, craft | Print styles: engraving, crosshatch, woodblock, risograph, stipple | Dark ink on the paper color; two inks for risograph | Fine (4–6px marks) | Little or no motion; Print shift at most |
54
+ | Techy, data, futuristic | Braille, blocks, dots, LED, hex | Source color with saturation 1.1–1.3, or a cool brand ink | 5–9px | Silk, Contour, Lens; field with `orb` or `flow` |
55
+ | Playful | Emoji or custom glyphs, mosaic, bricks, voxels | Source color | 9–14px | Elastic, Scatter, Ripple rings |
56
+ | Brand, identity, "our name in the art" | The brand word after a quiet ramp: `' .,:-+' + WORD` | One ink (`color: 'ink'`, `shade: 1`) so density and brightness carry the image | 10–14px; `cellAspect` 0.75–0.85 for display fonts | Field `hover: 'fluid'` (Unseen-like) or `'lantern'` (quieter); video or generated scene underneath |
57
+ | Behind body text | Light ramp, no dense glyphs | Ink at low contrast against the background; opacity 0.15–0.35 | 10–14px | Ambient only, or none; never strong refraction behind reading text |
58
+
59
+ Hover choice by intent:
60
+
61
+ - **Bold, liquid, "like unseen.co":** field `hover: 'fluid'`. The pointer stirs an incompressible fluid that bends the image and lights the characters in eddies. Use it on heroes and brand fields, not behind reading text.
62
+ - **Pure characters, the classic Asciify feel:** Trail. In the wake the characters fold their tone, sparse marks fill in and dense marks open up, then they settle; nothing bends. On `/field` use `hover: 'trail'` with `backdrop: 0` (GPU, 120 fps class); in `/studio` or `/hover` use `hover: { effect: 'trail' }` with a solid backdrop in the page color and `effects.characterBloom: 0`. Both run the same ink field with the same `strength` and `radius`, so the canvas renderer is a faithful fallback.
63
+ - **Subtle but premium, no watery distortion:** field `hover: 'lantern'`. A soft light glides after the pointer and lifts the characters it passes, with a short fading tail. Good near text, on product imagery and on calm brands.
64
+ - **Canvas pipelines (`/hover`, `/studio`):** Trail or Dissolve keep the image still; Water and Ripple are liquid; Magnetic and Scatter are playful; Lens magnifies detail.
65
+ - **Off:** when the art sits behind body text, in dense UIs, or when the user asks for none.
66
+
67
+ ## Living or still
68
+
69
+ A still photo can be made to feel alive, and many users want that. Others want it still. Decide from the user's words first, then the content and the context, and say which you chose.
70
+
71
+ **Make it live** when the user says alive, moving, dynamic, ambient or "like a video", and when the art is the subject: heroes, portfolios, brand moments, campaign pages, empty states.
72
+
73
+ **Keep it still** when the user says static, still, no animation or "just the effect"; when the art sits behind body text, forms or pricing; in product grids, dashboards and data views; for editorial and print looks; for exports, email and social images; on pages with many instances; and on low-power targets. Reduced motion always gets a still, whatever was chosen.
74
+
75
+ **The middle ground** is still until touched: no autonomous motion, only a hover. Lantern is the calmest version of this.
76
+
77
+ | API | Living | Still |
78
+ | --- | --- | --- |
79
+ | `/field` | `drift: 0.15–0.35` so a photo breathes; or a video `source`; or `scene: 'flow' \| 'orb'` with no media | A media `source`, `drift: 0`, and `hover: 'lantern'` or `'none'`. With no pointer it draws once and stops working |
80
+ | `/studio` | `motion: { type: 'sheen' \| 'breeze' \| 'relight' \| 'shimmer', speed: 0.4–0.8, amount: 0.3–0.6 }` for calm life; `'current'`, `'tidal'`, `'caustics'` for stronger; or a `STUDIO_ANIMATION_PRESETS` entry such as `quiet-light` | `motion: { type: 'none' }`, and `hover.effect: 'none'` or a stationary one such as `'trail'` |
81
+ | `/hover` over a core render | `motion: 'sheen'` (any `AmbientMotion`) | `motion: 'none'`; core `animationStyle: 'none'` |
82
+
83
+ Autonomous motion that lasts longer than five seconds needs a visible pause control (WCAG 2.2.2). Prefer slow speeds; motion should be noticed on the second look, not the first.
84
+
85
+ ## 5. Derive concrete values from the tokens
86
+
87
+ - **Ink.** One-ink looks use the accent token. Photo looks keep source color unless the brand is strictly monochrome.
88
+ - **Ground.** Use the page background token exactly, or a transparent canvas over it. On light themes draw dark ink on the light ground: core `invert: true` or `'auto'`, field `background: <page color>` with a dark `ink` and `invert: true`, studio dark `ink` with a light `backdrop.color`.
89
+ - **Backdrop.** `backdrop` puts a blurred copy of the source under the characters. It enriches a photo but reads as a pale photographic wash on light subjects. Leave it at 0 when the user wants pure ASCII, and never use it behind text.
90
+ - **Wake color.** Keep the field's default `highlight: 'auto'`, which brightens each character's own color. Only set a hex highlight when the brand asks for a second color; never default to white on a colored artwork.
91
+ - **Fonts.** Use the project's mono stack for ramps and its display face for words (`font`, `fontWeight`).
92
+ - **Size.** Start from the role in step 4, then check the narrowest supported viewport. Cells under 6px on phones turn to texture.
93
+ - **Contrast.** Text over art needs 4.5:1 at its busiest frame. Reach it with the scrim of the page background, lower opacity, vignette or `shade`, not by dimming the text.
94
+ - **Motion.** Match the project's easing and duration tokens for any UI around the canvas. Respect `prefers-reduced-motion`: stills or posters, no autonomous drift.
95
+
96
+ ## 6. Recipes
97
+
98
+ Each recipe states the request it answers. Replace token values with the project's.
99
+
100
+ **"Make our hero feel alive like unseen.co, with our name in it."**
101
+
102
+ ```ts
103
+ import { isGlyphFieldSupported, mountGlyphField } from 'asciify-engine/field';
104
+ const css = getComputedStyle(document.documentElement);
105
+ if (isGlyphFieldSupported()) {
106
+ const field = mountGlyphField(heroCanvas, {
107
+ source: '/media/hero-loop.mp4', // their footage; omit for a generated scene
108
+ charset: ' .,:-+' + 'NORTHWIND',
109
+ font: css.getPropertyValue('--font-display') || 'Inter, sans-serif',
110
+ cellSize: 12, cellAspect: 0.8,
111
+ color: 'ink', ink: css.getPropertyValue('--accent').trim(), shade: 1,
112
+ vignette: 0.35,
113
+ fluid: { radius: 0.08, refraction: 0.03, glow: 0.6 },
114
+ interactionTarget: heroSection,
115
+ label: 'Northwind, drawn in characters',
116
+ });
117
+ await field.ready; // then hide the poster
118
+ }
119
+ ```
120
+
121
+ **"Turn our product photo into ASCII that reacts to the mouse, keep its colors."**
122
+
123
+ ```ts
124
+ const field = mountGlyphField(canvas, {
125
+ source: '/img/product.jpg',
126
+ charset: ' .:*oO0@', font: css.getPropertyValue('--font-mono'),
127
+ cellSize: 8, cellAspect: 0.6,
128
+ color: 'source', backdrop: 0.35, saturation: 1.2, contrast: 1.2,
129
+ fluid: { radius: 0.085, refraction: 0.035, glow: 0.6 },
130
+ });
131
+ ```
132
+
133
+ For a quieter product page, add `hover: 'lantern'` (a soft light, no distortion) and keep `drift: 0` so the photo stays still until touched. Use `/studio` instead when they also want halftone, dither or an export button.
134
+
135
+ **"A subtle animated background behind our pricing section."** Download a template (for example Aurora or Silk) from asciify.org/backgrounds and mount it with the page's colors, low opacity and the cursor off:
136
+
137
+ ```js
138
+ import { asciiBackground } from './aurora.js';
139
+ const { destroy } = asciiBackground('#pricing', { colorScheme: 'auto', color: tokens.textMuted, opacity: 0.2, fontSize: 12, speed: 0.6 });
140
+ ```
141
+
142
+ **"Retro terminal look for our launch video."**
143
+
144
+ ```ts
145
+ import { mountStudio, studioLook } from 'asciify-engine/studio';
146
+ const player = await mountStudio(canvas, '/launch.mp4', { settings: { ...studioLook('terminal'), ink: tokens.accent }, maxCells: 20000 });
147
+ ```
148
+
149
+ **"An editorial, printed feel for the about page portrait."**
150
+
151
+ ```ts
152
+ const player = await mountStudio(canvas, '/portrait.jpg', {
153
+ settings: { style: 'engraving', colorMode: 'accent', ink: tokens.text, backdrop: { mode: 'solid', color: tokens.paper }, print: { density: 1, weight: 1, invert: true } },
154
+ });
155
+ ```
156
+
157
+ **"Our logo word as big ASCII text in the footer."**
158
+
159
+ ```ts
160
+ import { asciifyText, renderTextToCanvas } from 'asciify-engine';
161
+ footerPre.textContent = asciifyText('NORTHWIND', { char: '#', scale: 2 }); // plain text, copyable
162
+ // or, in color: renderTextToCanvas(canvas, 'NORTHWIND', { char: '█', scale: 2, color: tokens.accent });
163
+ ```
164
+
165
+ **"Same hero, but our site is light."**
166
+
167
+ ```ts
168
+ mountGlyphField(canvas, {
169
+ source: '/img/hero.jpg', background: tokens.paper, color: 'ink', ink: tokens.text, invert: true, shade: 0.8,
170
+ charset: ' .:-=+*#', cellSize: 10, cellAspect: 0.6,
171
+ });
172
+ ```
173
+
174
+ ## 7. Verify against the request
175
+
176
+ - Show the user what each mood word became, in one line, and offer one alternative (for example "denser" or "calmer") instead of a menu.
177
+ - Check the busiest frame for text contrast, the narrowest viewport, a light theme if the site has one, reduced motion, and the fallback path (no WebGL2 or software rendering for `/field`).
178
+ - Measure smoothness on the target device while moving the pointer, and report the device and settings with any number.
179
+ - Clean up on unmount and confirm nothing keeps drawing off screen.
@@ -0,0 +1,75 @@
1
+ # GPU glyph fields (4.11)
2
+
3
+ `asciify-engine/field` draws an image, a video or generated motion as characters entirely on the GPU, with a fluid wake under the cursor. Use it for full-bleed heroes, section backgrounds and brand fields where the frame must stay smooth while people move over it. It is a separate, optional entry; importing the root, `/core`, `/hover` or `/studio` never loads it.
4
+
5
+ Use `/studio` instead when you need the 24 render styles, dither, print styles, masks, effects or exports. The field draws glyph ramps only: characters, braille, block shading, dots, or your own letters.
6
+
7
+ ## Mount
8
+
9
+ ```ts
10
+ import { mountGlyphField, isGlyphFieldSupported } from 'asciify-engine/field';
11
+
12
+ const canvas = document.querySelector<HTMLCanvasElement>('#hero-field')!; // sized by CSS
13
+ if (isGlyphFieldSupported()) {
14
+ const field = mountGlyphField(canvas, {
15
+ source: '/media/hero.mp4', // image or video URL, or an element; omit for generated motion
16
+ charset: ' .,:-+ACME', // empty → dense; words work
17
+ cellSize: 12, // CSS px, cell height
18
+ color: 'ink', ink: '#f3ba32', shade: 1,
19
+ vignette: 0.3,
20
+ fluid: { radius: 0.08, refraction: 0.03, glow: 0.6 },
21
+ label: 'Our studio, drawn in our name',
22
+ });
23
+ await field.ready; // first frame drawn
24
+ // field.update({ ink: '#ffffff' }); field.pointer(x, y); field.pause(); field.destroy();
25
+ }
26
+ ```
27
+
28
+ Size the canvas with CSS (for example `position:absolute; inset:0; width:100%; height:100%`). The field follows the canvas with a `ResizeObserver` and caps the device pixel ratio at `maxPixelRatio` (2).
29
+
30
+ ## Options
31
+
32
+ | Option | Default | Notes |
33
+ | --- | --- | --- |
34
+ | `source` | none | URL (`.mp4`, `.webm`, `.mov` load as muted, looping, inline video), `HTMLImageElement`, `HTMLVideoElement`, `HTMLCanvasElement` or `ImageBitmap`. Cross-origin media needs CORS. |
35
+ | `scene` | `'flow'` | Generated motion when there is no source: `'flow'` (domain-warped noise), `'orb'` (lit sphere) or `'none'` (blank, wake only). |
36
+ | `charset` | `' .,:-=+*#%@'` | Ordered from empty to dense. Graphemes, so emoji, braille and block shading each take one cell. Up to 256. |
37
+ | `cellSize`, `cellAspect` | `12`, `1` | Cell height in CSS px; width ÷ height. Monospaced ramps look best near `0.6`. Cells are whole device pixels, and glyphs are sampled texel for texel. |
38
+ | `font`, `fontWeight`, `glyphScale` | monospace, `500`, `0.8` | Any loaded CSS font. The atlas is redrawn when the font finishes loading. |
39
+ | `color` | `'ink'` | `'ink'` uses `ink`; `'source'` takes each cell's hue from the media. |
40
+ | `ink`, `highlight` | `#f3ba32`, `'auto'` | Hex colors. The wake lifts characters toward `highlight`; `'auto'` brightens each character's own color so the wake keeps the artwork's hue. |
41
+ | `shade` | `0.5` | How much darker tones dim the ink, so same-density glyphs such as words still carry the image. |
42
+ | `saturation` | `1.1` | Source-color saturation. |
43
+ | `background` | `'transparent'` | Hex or `'transparent'`; the canvas is premultiplied. |
44
+ | `backdrop` | `0` | Opacity of a soft, blurred copy of the source under the glyphs. It is sampled from the mip chain, so it costs nothing extra. |
45
+ | `levels` | `'auto'` | Stretches the source's 2nd–98th luminance percentiles across the charset. Needs pixel access; tainted cross-origin media falls back to `'none'`. |
46
+ | `brightness`, `contrast`, `gamma`, `invert` | `0`, `1`, `1`, `false` | Applied after levels. |
47
+ | `fit`, `focus` | `'cover'`, centre | `focus` moves the cover crop (0–1 on each axis). |
48
+ | `vignette` | `0` | Darkens toward the corners. |
49
+ | `drift`, `speed` | `0`, `1` | Slow ambient sampling drift keeps a still image alive; `speed` scales drift and generated scenes. |
50
+ | `hover` | `'fluid'` | `'fluid'`: an ink-in-water wake that bends the image and swirls the characters. `'lantern'`: a soft light that follows the pointer and lifts characters with no distortion. `'trail'`: the Asciify Trail, the same ink field as the studio's Trail hover: in the wake, sparse characters fill in and dense ones open up, then settle, with no distortion. `'none'`: still under the pointer. `FIELD_HOVERS` lists them with labels. |
51
+ | `trail` | `{ strength: 0.55, radius: 0.45 }` | Same meaning and defaults as the studio's Trail hover, so a look moves between renderers unchanged. |
52
+ | `lantern` | soft | `radius` (fraction of height), `strength`, `trail` (seconds its tail takes to fade), `follow` (how tightly the light catches the pointer). |
53
+ | `fluid` | on | Settings for the fluid hover; `false` turns it off. `radius` (fraction of height), `force`, `dissipation` (per second; higher settles sooner), `curl` (vorticity: how strongly the wake curls into eddies, 0 is a straight smear), `refraction` (how far the flow bends sampling), `glow` (how strongly the flow lights characters), `resolution` (simulation grid, longest side). |
54
+ | `respectReducedMotion` | `true` | Under reduced motion, drift and generated scenes stop and the wake bends a quarter as far. Video still plays unless you pause it. |
55
+ | `interactionTarget` | `window` | Where pointer movement is read. Use the hero element when it dispatches synthetic pointer events. |
56
+
57
+ `update(options)` merges and rebuilds only what changed (the atlas for type changes, the simulation for resolution changes). `setSource(source)` swaps media. `pointer(x, y)` moves the wake in CSS pixels relative to the canvas (`pointer(null)` lifts it), which is useful for an intro stroke. `redraw()` draws immediately, so you can copy the canvas with `drawImage` in the same task, for example to cross-fade between looks.
58
+
59
+ ## Living or still
60
+
61
+ A photo with `drift: 0`, no video and a pointer that has left draws once and stops. Add `drift` (0.15–0.35) to let it breathe, use a video, or use a generated `scene` for continuous motion. Choose by the user's words and the context; [design-matching.md](design-matching.md#living-or-still) has the rules. Reduced motion stops drift and generated scenes; Lantern follows instantly and stops flickering.
62
+
63
+ ## Performance and lifecycle
64
+
65
+ - One fullscreen pass draws the characters. The wake is an incompressible fluid on a 128-cell grid: splat, vorticity confinement, 20 pressure iterations and advection, about 27 tiny passes while the pointer is active and none once it settles. On dark ground any motion lights the characters; on lit ground the flow brightens one side of each eddy and opens the other into gaps. GPUs without float render targets get a single-pass wake instead. Nothing is read back to the CPU per frame.
66
+ - A still image with no drift stops drawing once the wake settles. Videos upload only new frames (`requestVideoFrameCallback` where available). Off-screen and hidden-tab fields stop.
67
+ - Measured in Playwright Chromium (Metal) on an Apple M4 Pro at 1440 × 900, device pixel ratio 2: a full-viewport photo field held 120 fps with a 9 ms 95th-percentile frame while the pointer moved continuously. Measure your own target devices; the number depends on the GPU, canvas size and cell size.
68
+ - WebGL2 on a GPU is required. `isGlyphFieldSupported()` returns false when there is no WebGL2 or when it runs in software (SwiftShader, llvmpipe), because software rendering would starve the rest of the page; pass `{ allowSoftware: true }` to opt in, and use `glyphFieldTier()` to tell the cases apart. `mountGlyphField` throws when no context is available: catch it and render a poster or the `/studio` renderer instead.
69
+ - If frames stay slower than 24 fps for about two seconds, the field drops to 1× pixels, then pauses ambient drift and generated scenes. The wake and video keep working.
70
+ - Call `destroy()` on unmount. It removes listeners and observers and frees GPU resources; it keeps the context so a framework can remount on the same canvas.
71
+ - Lost contexts restore automatically when the browser allows it.
72
+
73
+ ## Accessibility
74
+
75
+ The canvas gets `role="img"` and `aria-label` from `label`. Keep real headings and text in the DOM above the field. Provide a static poster for no-JS and for the moment before `ready`.
@@ -10,6 +10,10 @@ A downloaded PNG is one output; it is not a substitute for a live component.
10
10
  | Multiple media/text layers with independent styles, placement, blends and exports | `/studio` → `mountStudioProject` (4.8+) | One shared loop and budget; see [layered-projects.md](layered-projects.md). |
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
+ | 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
+ | Ambient background with no media | Downloaded template from asciify.org/backgrounds, or a `/field` generated scene | Templates own their loop; call `destroy()` on unmount. |
15
+
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.
13
17
 
14
18
  ## Small live component
15
19
 
@@ -19,7 +23,7 @@ import { mountStudio, normalizeStudioSettings, updateStudioSettings } from 'asci
19
23
  const cancellation = new AbortController();
20
24
  let settings = normalizeStudioSettings({
21
25
  style: 'ascii', cellSize: 6,
22
- ink: '#b3ed82', backdrop: { mode: 'solid', color: '#0c1711' },
26
+ ink: tokens.accent, backdrop: { mode: 'solid', color: tokens.background }, // from the project's design tokens
23
27
  hover: { effect: 'trail', radius: .45, strength: .55 },
24
28
  });
25
29
  const player = await mountStudio(canvas, '/portrait.jpg', {
@@ -43,6 +43,9 @@ The positioned host must fit the displayed source canvas. The surface appends a
43
43
  | `etch` | Signed light-and-shadow impression; the glyph grid stays fixed. |
44
44
  | `elastic` | Directional momentum, damped recoil and a return to rest. |
45
45
  | `rake` | Alternating strips shear with the pointer, then align again. |
46
+ | `lens` | Smooth local magnification, relaxing when the pointer stops. |
47
+ | `smudge` | Directional source drag with a non-oscillating release. |
48
+ | `ripple` | Expanding signed tonal wave packets; speed-sensitive and fixed-grid. |
46
49
  | `none` | No hover effect. |
47
50
 
48
51
  These are `/hover` module `effect` IDs, not the root engine’s legacy `HoverEffect` values. For example, passing `hoverEffect: 'water'` to the published engine is invalid.
@@ -75,3 +78,29 @@ For a fixed grid, try Contour Light with Emboss. For continuous spatial movement
75
78
  All ambient modes repeat after `12 / speed` seconds. Looping the motion does not guarantee that a source video, film grain, dust, glitch or animated dither has the same boundary. Exported PNG/JPEG captures one frame; MP4/WebM/GIF carries autonomous motion, not live pointer movement. For complete composition export, use `/studio` rather than capturing the hover overlay.
76
79
 
77
80
  The pointer simulations have bounded storage. Elastic allocates its two velocity buffers only when first selected; idle effects stop. This is not a device-independent FPS promise. Test dense grids, fast reversal and the actual finish stack on the target device.
81
+
82
+ ## Lively images (4.10)
83
+
84
+ The catalogs contain fourteen active hover effects and fourteen active ambient motions, plus Off for each. New motion IDs are `relight`, `shimmer`, `breeze`, and `unfold`. Relight models midtones with a rotating light field; Shimmer slowly animates source highlights. Both keep geometry stationary. Breeze bends the image with a traveling sway. Unfold closes and opens staggered panels, then holds the complete image. All use deterministic 12-second base cycles; `motion.speed` changes cycle duration, `motion.amount` changes strength. These are local image treatments, not AI-generated scene or subject motion.
85
+
86
+ `STUDIO_ANIMATION_PRESETS` is exported by `/studio` and `/hover`. Its six entries have `id`, `label`, `description`, `motion: {type,speed,amount}`, and `hover: {effect,radius,strength}`. These are editable combinations, not additional renderer styles. Do not overwrite the rest of a user's settings to apply one:
87
+
88
+ ```ts
89
+ import { mountStudio, STUDIO_ANIMATION_PRESETS } from 'asciify-engine/studio';
90
+ const preset = STUDIO_ANIMATION_PRESETS.find(item => item.id === 'quiet-light')!;
91
+ const player = await mountStudio(canvas, '/photo.webp', {
92
+ settings: { style: 'ascii', cellSize: 6, motion: preset.motion, hover: preset.hover },
93
+ maxDimension: 960,
94
+ });
95
+ player.update({ motion: { amount: .6 }, hover: { radius: .55 } });
96
+ // Stop on unmount. Do not mount a second /hover instance on this canvas.
97
+ player.destroy();
98
+ ```
99
+
100
+ For a complete integration with independent PNG/MP4 export, copy the repository's `examples/living-image.ts` and `examples/studio-composition.ts`. Keep their relative import together; these helpers are example application code, not npm exports. Saved Studio state stores the resolved settings, so later catalog edits do not change a saved look.
101
+
102
+ For `/hover`, the same catalog needs an explicit shape conversion: `surface.update({ motion: preset.motion.type, motionSpeed: preset.motion.speed, effect: preset.hover.effect, radius: preset.hover.radius, strength: preset.hover.strength })`. `/hover` has no `motion.amount` option; use Studio for independently adjustable motion strength. Do not spread the nested Studio groups directly into surface options.
103
+
104
+ Ripple rings uses at most eight wave packets and a fixed-resolution field. It does not allocate particles per character. Lens and Smudge reuse the existing field; their release integrates elapsed time, not a fixed frame count. Zero strength suppresses hover input; completed wakes sleep. These bounds and tests are not a guaranteed frame rate on every device.
105
+
106
+ To export a loop of a photo, use duration `12 / speed`. Restrict the speed/duration to your encoder's limits and use a frame count that fits whole cycles. Film grain, dust, glitch, pattern animation and input videos may not loop at that boundary. Pointer paths are not included in offline export. Respect reduced motion and provide pause; do not advertise artificial depth as true scene reconstruction.
@@ -182,7 +182,7 @@ Stationary artwork, masks, lights, backdrops and character bloom are cached whil
182
182
 
183
183
  ## Expanded motion library (4.4)
184
184
 
185
- Use `STUDIO_HOVERS` and `STUDIO_MOTIONS` to show all eleven hovers and ten still-image motions, plus Off, grouped by behavior. New hover IDs are `etch`, `elastic`, `rake`; new motion IDs are `parallax`, `weave`, `print`, `trace`. `hover.radius` and `hover.strength` are independent of `motion.speed` and `motion.amount`. All remain serialized in look links and saved projects. A static photo needs no video input or AI service to animate.
185
+ Use `STUDIO_HOVERS` and `STUDIO_MOTIONS` to show all available hovers and still-image motions, plus Off, grouped by behavior. New hover IDs are `etch`, `elastic`, `rake`; new motion IDs are `parallax`, `weave`, `print`, `trace`. `hover.radius` and `hover.strength` are independent of `motion.speed` and `motion.amount`. All remain serialized in look links and saved projects. A static photo needs no video input or AI service to animate.
186
186
 
187
187
  Example: `player.update({ motion: { type: 'trace', speed: 1, amount: .7 }, hover: { effect: 'etch', radius: .45, strength: .65 } })`. Tone and character choices remain yours. See [effect behaviors and combinations](studio-effects.md#catalogs-and-useful-combinations-44). Do not run several looping thumbnail canvases just to display this catalog; apply the selected treatment to one shared preview.
188
188
 
@@ -247,3 +247,7 @@ const saved = serializeStudioSettings(player.settings);
247
247
  ## Print and illustration (4.9)
248
248
 
249
249
  The catalog adds six print treatments with fine square cells and an optional `print` settings group. See [print-styles.md](print-styles.md) for the mechanisms, controls, polarity, bounded rendering and living-image example. They use the same Studio pipeline, saved settings and exports.
250
+
251
+ ## Animation starting points (4.10)
252
+
253
+ Fourteen hover responses and fourteen local still-image motions are available through the public catalogs. The editor's Effects panel and layered editor's Motion panel share all choices, independent size/strength/speed controls and six editable starting combinations from `STUDIO_ANIMATION_PRESETS`. See [lively images](studio-effects.md#lively-images-410). Existing defaults stay unchanged; animate a photograph deliberately rather than enabling motion for every upload.