asciify-engine 1.0.118 → 1.1.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.
@@ -1,801 +1,128 @@
1
1
  ---
2
2
  name: asciify-engine
3
- description: Convert images, videos, GIFs, webcam streams, text, and animated backgrounds into high-performance ASCII art with the asciify-engine npm package. Use when creating ASCII media, integrating asciifyVideo/asciify/asciifyGif, building scroll-synced ASCII video, tuning FPS/detail/performance, using compact text-frame APIs, hover effects, chroma key, recording/export, or updating an app that depends on 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.
4
+ metadata:
5
+ tested-engine: "1.1.0"
6
+ updated: "2026-09-08"
4
7
  ---
5
8
 
6
- # asciify-engine
9
+ # Asciify Engine
7
10
 
8
- Use this skill for the `asciify-engine` npm package.
11
+ Use the published `asciify-engine` npm package for browser canvas rendering. Tested against **1.1.0**. Check the application's installed version and its public types before using newer options; a local checkout can contain unpublished changes.
9
12
 
10
- **Package:** `asciify-engine`
11
- **Current version:** `1.0.115`
12
- **Playground:** https://asciify.org
13
- **GitHub:** https://github.com/ayangabryl/asciify-engine
13
+ - Agent entry: https://asciify.org/skill (redirects to this Markdown document).
14
+ - Playground: https://asciify.org/editor
15
+ - API documentation: https://asciify.org/docs/api
16
+ - Repository: https://github.com/ayangabryl/asciify-engine
14
17
 
15
- The engine is zero-dependency except GIF decoding. It renders ASCII art on canvas from images, videos, GIFs, webcam streams, generated backgrounds, and text.
16
-
17
- ## When To Use
18
-
19
- Use this skill when the user wants to:
20
-
21
- - Convert images, videos, GIFs, webcam, or text into ASCII art.
22
- - Add ASCII video/backgrounds to React, Next.js, Vue, Svelte, or vanilla JS.
23
- - Build scroll-synced ASCII video with GSAP ScrollTrigger or native scroll.
24
- - Improve ASCII media performance, FPS, detail, resolution, or hover behavior.
25
- - Use chroma key, fullcolor, accent, matrix, grayscale, dense/braille charsets, or hover effects.
26
- - Use low-level prepared/compact frame APIs.
27
- - Record/export ASCII canvas output.
28
- - Update docs, package usage, or skill instructions for `asciify-engine`.
29
-
30
- ## Installation
31
-
32
- ```bash
33
- npm install asciify-engine
34
- ```
35
-
36
- For existing apps, install the latest tested version:
37
-
38
- ```bash
39
- npm install asciify-engine@1.0.115
18
+ ```sh
19
+ npm install asciify-engine@1.1.0
40
20
  ```
41
21
 
42
- ## Mental Model
43
-
44
- Prefer the simple APIs first:
45
-
46
- - `asciify()` for static images/canvas/video elements.
47
- - `asciifyVideo()` for live or scroll-synced video.
48
- - `asciifyGif()` for animated GIF files.
49
- - `asciiBackground()` for procedural ASCII backgrounds.
50
-
51
- For high-performance media, the engine uses compact text-frame paths when possible:
52
-
53
- - `imageToAsciiTextFrame()`
54
- - `videoToAsciiTextFrames()`
55
- - `gifToAsciiTextFrames()`
56
- - `renderTextFrameToCanvas()`
22
+ Version 1.1.0 exports `asciify-engine`, `asciify-engine/core` for media without procedural generators, and `asciify-engine/backgrounds` for optional backgrounds. Root imports remain supported. GIF decoding loads `gifuct-js` when a GIF API is called. Site-internal studio modules are separate.
57
23
 
58
- These avoid object-per-cell frames and are the preferred low-level APIs for dense media, including fullcolor in `1.0.91+`.
24
+ ## Install the agent skill
59
25
 
60
- Use legacy object frames only when you need per-cell mutation, dots mode, heavy hover/animation transforms, or compatibility with existing `AsciiFrame` consumers:
61
-
62
- - `imageToAsciiFrame()`
63
- - `videoToAsciiFrames()`
64
- - `gifToAsciiFrames()`
65
- - `renderFrameToCanvas()`
66
-
67
- Use `options.sourceCrop` when the source media has empty edges or needs tighter framing. Percent values are normalized `0–1` by default. Prefer CSS-like insets for readable framing; side insets define the crop window directly, and `asciifyVideo` sizes from that crop aspect so it does not stretch:
68
-
69
- ```ts
70
- options: {
71
- sourceCrop: { top: 0.08, bottom: 0.22 },
72
- }
26
+ ```sh
27
+ npx skills add ayangabryl/asciify-engine --skill asciify-engine
73
28
  ```
74
29
 
75
- Crop from the sides the same way:
30
+ This uses the Vercel Skills CLI to install the repository skill and its linked resources. Updating the npm dependency alone does not update an already installed skill. Use `npx skills update asciify-engine` to refresh it.
76
31
 
77
- ```ts
78
- options: {
79
- sourceCrop: { left: 0.1, right: 0.1 },
80
- }
81
- ```
82
-
83
- Use any side together:
32
+ ## Choose the integration
84
33
 
85
- ```ts
86
- options: {
87
- sourceCrop: { top: 0.08, right: 0.12, bottom: 0.22, left: 0.12 },
88
- }
89
- ```
34
+ - Static image: `asciify(source, canvas, config)` returns `Promise<() => void>`. Call the cleanup on unmount, including for a still image.
35
+ - Video: `asciifyVideo(source, canvas, config)` returns `Promise<() => void>`. Call that function on unmount. Use `fitTo` and `objectFit: 'cover'` for full-bleed heroes; `contain` for a whole-subject preview.
36
+ - GIF: `asciifyGif(source, canvas, config)` returns a cleanup function asynchronously.
37
+ - Procedural background: `asciiBackground(host, config)` returns an object with **`destroy()`**, not a stop function. Media conversion does not require adding a procedural background.
38
+ - Dense custom rendering: use `imageToAsciiTextFrame` and `renderTextFrameToCanvas`; keep the same options when converting and rendering. Use object frames when per-cell mutation is required.
90
39
 
91
- For centered crops, `sourceCrop: { height: 0.7 }` keeps 70% of the source and preserves the original source aspect. `preserveAspect` is only for single-dimension `width`/`height` crops; do not use it to force hero bands. For wide heroes, crop with `top`/`bottom` and let the engine derive the cropped aspect. `sourceCrop` is opt-in; when omitted, the engine samples the full source exactly as older versions did.
40
+ For a hero with HTML layered through the ASCII, read [references/layered-hero.md](references/layered-hero.md) and use the accompanying [HTML](examples/layered-hero.html), [CSS](examples/layered-hero.css), and [JavaScript](examples/layered-hero.js). These are ordinary Vite/bundler inputs, not another npm dependency. Supply your own media.
92
41
 
93
- When `sourceCrop` and `chromaKey` are both enabled for video, `asciifyVideo` does a one-time foreground trim inside the crop window. For scrubbed or trimmed video it samples the playback range and uses the union of the keyed foreground bounds. This removes keyed empty margins before layout sizing, which is the right fix when the canvas is full width but the visible ASCII subject still appears short on one side.
42
+ For crop, scroll, React lifecycle, and low-level API details, read [references/media-api.md](references/media-api.md).
94
43
 
95
- Use `chromaKeyTrimPadding: 0` for edge-tight marketing heroes. Leave the default `0.002` when preserving a tiny safety margin matters more than touching the exact viewport edge.
96
- If the canvas is full width but the visible keyed subject still feels short, raise `chromaKeyTrimLuminanceThreshold` slightly, usually `6–16`, so invisible edge noise is not counted as foreground.
44
+ ## Start with a readable source
97
45
 
98
- For scroll-scrubbed hero footage where the foreground moves and a single range crop still leaves one frame short on the edge, use per-frame trim:
46
+ Use ordinary images or footage, then convert them with Asciify. Do not ask an image/video generator to pre-render ASCII. A dark subject with thin highlights tends to become an outline; changing to full color cannot recover missing midtones. Prefer recognizable subjects, broad illuminated surfaces, controlled highlights, and stable framing.
99
47
 
100
- ```ts
101
- options: {
102
- chromaKey: true,
103
- sourceCrop: { top: 0.08, bottom: 0.16 },
104
- chromaKeyTrimMode: 'frame',
105
- chromaKeyTrimPadding: 0,
106
- }
107
- ```
48
+ Choose color intentionally:
108
49
 
109
- `chromaKeyTrimMode: 'frame'` remeasures the visible keyed foreground on each rendered frame and expands that crop back to the render aspect, so it avoids stretching while keeping moving subjects visually edge-tight. Use it for dense scrubbed marketing hero media; keep the default `'range'` for normal video when stable framing matters more.
50
+ - `fullcolor`: preserve source colors; do not grayscale source pixels first.
51
+ - `grayscale`: gray characters.
52
+ - `accent`: a single brand color via `accentColor`.
53
+ - `matrix`: green treatment.
110
54
 
111
- Use top-level `asciifyVideo` layout options when the destination needs full-bleed framing. These affect the visible canvas only, not the sampled media:
55
+ Start around `fontSize: 7`, `fps: 24` or `30`, and `maxRenderDimension: 960`. These are a starting budget, not an FPS guarantee. Increase detail only after checking the actual viewport and target hardware. Use `normalize: true` for flat sources when helpful; compare it against `false` on already graded video because frame-by-frame normalization may change the look.
112
56
 
113
- ```ts
114
- await asciifyVideo('/hero.mp4', canvas, {
115
- fitTo: hero,
116
- objectFit: 'contain',
117
- objectPosition: 'center bottom',
118
- width: '100vw',
119
- height: '100%',
120
- bleed: { x: '2vw' },
121
- options: {
122
- sourceCrop: { top: 0.08, bottom: 0.14 },
123
- },
124
- });
125
- ```
126
-
127
- Mental split:
128
-
129
- - `sourceCrop` chooses the source window before ASCII conversion.
130
- - CSS-like insets change the crop aspect naturally; the engine keeps ASCII proportions intact.
131
- - `objectFit`, `objectPosition`, `scale`, `width`, `height`, and `bleed` place the rendered canvas in the page.
132
- - Use `objectFit: 'cover'` for full-width/full-bleed heroes and backgrounds.
133
- - Use `objectFit: 'contain'` for previews where the whole source should remain visible.
134
- - Use `bleed: { x: '2vw' }` for edge-tight ASCII heroes where the source is correct but glyph side bearings or cell quantization leave a thin visual edge gap. This is preferable to app CSS hacks like `width: 104vw`.
135
- - Use `scale` only when you intentionally want the whole ASCII canvas visually larger.
136
-
137
- ## Visual Target
138
-
139
- Good ASCII media should feel intentional, not like a broken video filter.
140
-
141
- - The subject silhouette is readable at first glance.
142
- - Character density reveals form, light, and motion without turning into noise.
143
- - The canvas respects the media aspect ratio and fills the intended container.
144
- - Accent/fullcolor choices support the page design instead of shouting over it.
145
- - Text is crisp, not browser-blurred, stretched, or visibly low resolution.
146
- - Motion updates feel continuous; if FPS drops, reduce work before adding effects.
147
-
148
- ## Decision Playbooks
149
-
150
- ### User Wants More Detail
151
-
152
- Do not only reduce `fontSize`. Use this ladder:
153
-
154
- 1. Confirm the canvas/container is actually large enough and not CSS-stretched from a tiny backing buffer.
155
- 2. Use `fitTo` for videos so the engine owns canvas sizing.
156
- 3. Use `CHARSETS.dense` for normal ASCII detail.
157
- 4. Enable `normalize: true` for muted or low-contrast sources.
158
- 5. Raise `maxRenderDimension` one step: `960 -> 1280 -> 1600 -> 2048`.
159
- 6. Then lower `fontSize`: `8 -> 6 -> 5 -> 4`.
160
- 7. Try `colorMode: 'accent'` before `fullcolor` if the design can be monochrome.
161
- 8. Use `CHARSETS.braille` only when ultra-detail matters and the style can handle the texture.
162
-
163
- Stop when the subject reads clearly. More cells after that often make the image noisier, not better.
164
-
165
- ### User Wants Smoother FPS
166
-
167
- Reduce hot-path work in this order:
168
-
169
- 1. Keep `animationStyle: 'none'` and `hoverStrength: 0`.
170
- 2. Use compact text-frame-compatible settings: `renderMode: 'ascii'`, no `charsetFrames`.
171
- 3. Prefer `accent`, `matrix`, or `grayscale`; keep `fullcolor` only when color matters.
172
- 4. Lower `maxRenderDimension`: `2048 -> 1600 -> 1280 -> 960`.
173
- 5. Raise `fontSize`: `4 -> 5 -> 6 -> 8`.
174
- 6. Lower `fps`: `60 -> 30 -> 24`.
175
- 7. Avoid `dots`, per-cell hover, and animated charsets on dense video.
176
-
177
- If FPS is still poor, inspect whether the app is resizing the canvas every frame or remounting the component.
178
-
179
- ### User Wants A Premium Hero
180
-
181
- Recommended starting point:
182
-
183
- ```ts
184
- {
57
+ ```js
58
+ import { asciifyVideo } from 'asciify-engine';
59
+ const stop = await asciifyVideo('/hero.mp4', canvas, {
185
60
  fitTo: hero,
186
- objectFit: 'contain',
187
- objectPosition: 'center bottom',
188
- width: '100vw',
189
- height: '100%',
190
- bleed: { x: '2vw' },
191
- fontSize: 5,
192
- fps: 60,
193
- maxRenderDimension: 1280,
194
- maxCachedFrames: 96,
195
- artStyle: 'classic',
61
+ objectFit: 'cover',
62
+ objectPosition: 'center',
63
+ fontSize: 7,
64
+ fps: 24,
65
+ maxRenderDimension: 960,
196
66
  options: {
197
- charset: CHARSETS.dense,
198
- colorMode: 'accent',
199
- accentColor: '#e8d7b7',
200
- normalize: true,
201
- sourceCrop: { top: 0.08, bottom: 0.16 },
202
- animationStyle: 'none',
203
- hoverStrength: 0,
204
- }
205
- }
206
- ```
207
-
208
- Design rules:
209
-
210
- - Use one restrained accent color pulled from the brand/page palette.
211
- - Put ASCII behind or below the core CTA, not directly under tiny text.
212
- - Use masks or fades at edges when the ASCII meets page content.
213
- - Keep the hero background quiet enough that the headline wins.
214
- - Use scroll scrub only when it tells a story; do not scrub decorative noise.
215
-
216
- ### User Wants Fullcolor
217
-
218
- Use fullcolor when color carries product meaning, artwork, identity, or footage mood.
219
-
220
- Start with:
221
-
222
- ```ts
223
- {
224
- fontSize: 6,
225
- fps: 30,
226
- maxRenderDimension: 1280,
227
- artStyle: 'art',
228
- options: {
229
- charset: CHARSETS.dense,
67
+ charset: ' .:-=+*#%@',
230
68
  colorMode: 'fullcolor',
231
- normalize: true,
232
- sourceCrop: { top: 0.08, bottom: 0.16 },
69
+ normalize: false,
233
70
  animationStyle: 'none',
234
71
  hoverStrength: 0,
235
- }
236
- }
237
- ```
238
-
239
- If it is slow, first lower `maxRenderDimension` or raise `fontSize`. Do not jump straight to `fontSize: 3`.
240
-
241
- ### User Wants Hover Interaction
242
-
243
- Hover is per-cell work. Keep it deliberate:
244
-
245
- - Use `fontSize >= 8` for large canvases.
246
- - Use smaller framed demos rather than full-screen 4px ASCII.
247
- - Start with `HOVER_PRESETS.subtle` or `hoverStrength: 0.2`.
248
- - Avoid hover on scroll-synced video unless the canvas is small.
249
- - Prefer hover for static images, generated text backgrounds, or product demos.
250
-
251
- ### User Says It Is Blurry
252
-
253
- Check in this order:
254
-
255
- 1. Is the canvas CSS size different from its backing `width`/`height`?
256
- 2. For video, is `fitTo` provided?
257
- 3. Is the source itself low resolution?
258
- 4. Is `maxRenderDimension` too low for the displayed size?
259
- 5. Is CSS applying `filter`, `transform: scale`, or opacity compositing that softens text?
260
- 6. For tiny font ASCII, verify the design accepts raster-like pixel detail.
261
-
262
- ### User Says It Is Blank
263
-
264
- Check:
265
-
266
- 1. Source loaded and video has metadata.
267
- 2. Canvas has nonzero width/height.
268
- 3. Video is not `display: none`.
269
- 4. `chromaKey` is not removing the subject.
270
- 5. `invert` and page background are not making characters invisible.
271
- 6. For async React use, cleanup did not run before `asciifyVideo()` resolved.
272
-
273
- ### User Says It Is Too Large Or Overflows
274
-
275
- Use layout fixes before quality fixes:
276
-
277
- - Wrap canvas in a positioned container.
278
- - Set canvas CSS to `position:absolute; inset:0; width:100%; height:100%`.
279
- - Use `fitTo: container`.
280
- - Keep the parent aspect ratio or explicit height stable.
281
- - Avoid manually setting `canvas.width = window.innerWidth` in React render.
282
-
283
- ### User Wants To Remove Empty Space In The Media
284
-
285
- Prefer source cropping over layout hacks when the video/image itself has dead space:
286
-
287
- ```ts
288
- await asciifyVideo(video, canvas, {
289
- fitTo: hero,
290
- options: {
291
- chromaKey: true,
292
- sourceCrop: { top: 0.08, bottom: 0.22 },
293
72
  },
294
73
  });
74
+ // On unmount: stop();
295
75
  ```
296
76
 
297
- - `sourceCrop` happens before ASCII sampling, so the subject is reframed without changing the canvas box.
298
- - Default unit is `'percent'`; use values from `0` to `1`.
299
- - Use `top`, `right`, `bottom`, and `left` for CSS-like side crops.
300
- - Use `{ unit: 'pixel', x, y, width, height }` for exact media-pixel crops.
301
- - Keep `preserveAspect` enabled unless you intentionally want an exact source window that can stretch.
302
- - For hero videos, crop the source first, then use small layout overlap only if the next section needs to tuck closer.
303
-
304
- ### Scroll-Scrub Performance Model
305
-
306
- In `1.0.93+`, the fast text-frame scrub path uses a Pretext-inspired split:
77
+ ## Custom letters
307
78
 
308
- - Prepare/cache frames lazily around the current scroll target.
309
- - Render compact row strings instead of object-per-cell frames.
310
- - Diff transparent rows and only redraw rows whose text changed.
311
- - Prefer `fastSeek()` when the browser supports it.
312
- - Prefetch nearby frames around the current target so scrolling back and forth has fewer misses.
79
+ `options.charset` is the density ramp. `customText` repeats a string and is a different feature. To incorporate a brand's letters while retaining shading symbols, build a ramp such as:
313
80
 
314
- For smooth scrub, keep options compatible with this fast path:
315
-
316
- ```ts
317
- {
318
- renderMode: 'ascii',
319
- animationStyle: 'none',
320
- hoverStrength: 0,
321
- colorMode: 'accent', // or grayscale/matrix
322
- }
81
+ ```js
82
+ const letters = [...new Set('ASCIIFY'.toUpperCase().replace(/[^A-Z]/g, ''))].join('');
83
+ const charset = ` .:-=+${letters}#%@`;
323
84
  ```
324
85
 
325
- ## Design Playbooks
326
-
327
- ### Dark Premium Landing Page
328
-
329
- - Use `#121212` or near-black page background.
330
- - Prefer `accent` over `fullcolor` unless the footage itself is beautiful.
331
- - Use warm off-white or brand accent, not neon by default.
332
- - Pair ASCII with framed layout, thin borders, or quiet masks.
333
- - Keep copy short; ASCII should carry some of the storytelling.
86
+ 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 `CHARSETS.standard`, `dense`, `blocks`, or `braille` when they better suit the subject.
334
87
 
335
- ### Technical / Infrastructure Aesthetic
88
+ ## Engine effects versus studio effects
336
89
 
337
- - Use `matrix`, `grayscale`, or a cool `accentColor`.
338
- - Use `CHARSETS.dense`, `lines`, `box`, or `geometric`.
339
- - Compose with visible grid lines, clipped containers, and measured spacing.
340
- - Avoid playful emoji/katakana unless the brand asks for it.
90
+ The public npm `HoverEffect` IDs in 1.1.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**.
341
91
 
342
- ### Editorial / Portfolio Aesthetic
92
+ The website offers **Trail, Water, Contour, Dissolve, Silk, Vortex, and Off** through an optional studio surface module. Its Living Print, Slow Current, Reveal & Reform, and fine-dither treatments are also site features; do not invent npm options from their display names.
343
93
 
344
- - Use `accent` with a soft paper/warm ink palette.
345
- - Use slower video, stills, or subtle scroll reveal.
346
- - Keep `fontSize: 5-8`; avoid over-dense unreadable texture.
347
- - Use ASCII as proof/atmosphere around work, not as a gimmick.
94
+ To reproduce those interactions, read [references/studio-effects.md](references/studio-effects.md). The module is downloadable application code. 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.
348
95
 
349
- ### Playground / Demo Aesthetic
96
+ 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.
350
97
 
351
- - Show controls that map to real engine tradeoffs: detail, FPS, color, charset, hover.
352
- - Surface live `cols x rows`, FPS, and current source dimensions.
353
- - Include presets: `Smooth`, `Detailed`, `Fullcolor`, `Interactive`.
354
- - When changing presets, explain the performance tradeoff in the UI.
98
+ ## Text layering
355
99
 
356
- ## Quality Checks
100
+ Use real HTML for the headline, description, and links. The engine draws artwork; CSS controls which HTML sits in front of it.
357
101
 
358
- - Subject is recognizable in a screenshot.
359
- - FPS is stable for 5-10 seconds, not just on first load.
360
- - The canvas does not resize every frame.
361
- - Text/CTA remains readable over or near the ASCII.
362
- - Mobile uses a less expensive preset than desktop when needed.
363
- - Reduced-motion users still get a static or slower frame.
364
- - Cleanup runs on unmount and cancels video/background loops.
102
+ For our cover relationship, use a single isolated hero stacking context:
365
103
 
366
- ## Simple APIs
104
+ 1. Headline at `z-index: 0`.
105
+ 2. Transparent ASCII canvas/stage at `z-index: 1`.
106
+ 3. Description, CTA, and motion controls at `z-index: 2` or above.
367
107
 
368
- ### Static Image
108
+ Keep these as sibling layers. A transformed or isolated content wrapper can trap all its descendants below the canvas. Set decorative artwork to `pointer-events: none`; attach optional hover input to the positioned hero host. Keep controls clickable, focusable, and visually unobscured. Leave canvas background transparent; an opaque canvas or stage hides the headline beneath it.
369
109
 
370
- ```ts
371
- import { asciify, CHARSETS } from 'asciify-engine';
372
-
373
- await asciify('/photo.jpg', canvas, {
374
- fontSize: 6,
375
- artStyle: 'art',
376
- options: {
377
- charset: CHARSETS.dense,
378
- colorMode: 'fullcolor',
379
- normalize: true,
380
- },
381
- });
382
- ```
110
+ Choose the more conventional arrangement—artwork behind all text—when it improves readability. The layered example explains both arrangements without imposing the Asciify homepage's branding or layout on another project.
383
111
 
384
- ### Live Video
112
+ ## Performance and lifecycle
385
113
 
386
- ```ts
387
- import { asciifyVideo, CHARSETS } from 'asciify-engine';
114
+ - Reduce processing dimensions and cell count before dropping source playback FPS. Do not default to 60 fps on a 24 fps video.
115
+ - 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.1.0 includes the renderer fixes previously applied by the website; it does not include the optional studio surface module.
116
+ - Keep stable canvas dimensions; resize on layout changes, not on every animation frame.
117
+ - Avoid remounting the renderer on pointer movement or each React render.
118
+ - 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.
119
+ - Guard asynchronous setup: if a component unmounts before an API resolves, immediately call its returned cleanup. Handle loading failures without removing the HTML content or CTA.
120
+ - Compress media at a resolution appropriate to ASCII sampling, remove unused audio, use fast-start MP4, and provide a small poster. Immutable caching requires a content-hashed URL; keep this skill's URL revalidatable.
121
+ - Fine dithering can add texture but can shimmer on moving footage. Compare a still and a loop, and offer a non-dithered rendering.
122
+ - YouTube page URLs and iframe players are not direct canvas video sources. Use permitted direct media files with suitable CORS; see https://asciify.org/docs/youtube.
388
123
 
389
- const stop = await asciifyVideo('/clip.mp4', canvas, {
390
- fitTo: container,
391
- fontSize: 6,
392
- fps: 60,
393
- maxRenderDimension: 1280,
394
- artStyle: 'classic',
395
- options: {
396
- charset: CHARSETS.dense,
397
- colorMode: 'accent',
398
- accentColor: '#e8d7b7',
399
- normalize: true,
400
- },
401
- });
402
-
403
- // cleanup
404
- stop();
405
- ```
406
-
407
- ### GIF
408
-
409
- ```ts
410
- import { asciifyGif } from 'asciify-engine';
411
-
412
- const stop = await asciifyGif('/loop.gif', canvas, {
413
- fontSize: 8,
414
- artStyle: 'letters',
415
- options: { colorMode: 'fullcolor' },
416
- });
417
-
418
- stop();
419
- ```
124
+ ## Verify before handing off
420
125
 
421
- ## Scroll-Synced Video
126
+ 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.
422
127
 
423
- `asciifyVideo()` supports scroll scrubbing. Use this for landing-page hero reveals, editorial sections, and pinned/scrollytelling moments.
424
-
425
- ### GSAP ScrollTrigger
426
-
427
- ```ts
428
- import gsap from 'gsap';
429
- import { ScrollTrigger } from 'gsap/ScrollTrigger';
430
- import { asciifyVideo, CHARSETS } from 'asciify-engine';
431
-
432
- const stop = await asciifyVideo('/hero.mp4', canvas, {
433
- fitTo: wrapper,
434
- fontSize: 4,
435
- fps: 60,
436
- maxRenderDimension: 1280,
437
- trim: { start: 2.2, end: 6.65 },
438
- scroll: {
439
- gsap,
440
- ScrollTrigger,
441
- trigger: wrapper,
442
- start: 'top 88%',
443
- end: 'bottom 18%',
444
- scrub: 0.45,
445
- speed: 1.35,
446
- from: 2.2,
447
- to: 6.65,
448
- },
449
- options: {
450
- charset: CHARSETS.dense,
451
- colorMode: 'accent',
452
- accentColor: '#e8d7b7',
453
- normalize: true,
454
- chromaKey: true,
455
- chromaKeyTolerance: 84,
456
- },
457
- });
458
- ```
459
-
460
- ### Native Scroll Scrub
461
-
462
- ```ts
463
- const stop = await asciifyVideo('/hero.mp4', canvas, {
464
- fitTo: wrapper,
465
- fontSize: 6,
466
- scroll: {
467
- trigger: wrapper,
468
- from: 0,
469
- to: 5,
470
- },
471
- });
472
- ```
473
-
474
- ### Scroll Best Practices
475
-
476
- - Use `scroll` for time-to-scroll mapping, not custom `video.currentTime` loops.
477
- - Pass the same `from`/`to` as `trim.start`/`trim.end` when scrubbing a clipped range.
478
- - Use `scroll.speed` for faster or slower scrub pacing. `speed: 2` reaches the end in half the scroll distance; `speed: 0.5` takes twice the distance.
479
- - Use `fps: 30-60`; choose `60` for smooth short hero clips, `24-30` for heavy fullcolor.
480
- - Use `maxRenderDimension: 960-1280` for large full-width hero videos. Use `2048` only when truly needed.
481
- - Use `fontSize: 4-6` for detailed hero ASCII; below `4px` is expensive and should use compact text-frame paths only.
482
- - Avoid hover effects on dense scroll video. Hover is per-cell work.
483
- - Never set the backing video to `display: none`; the API keeps URL-created videos decode-safe automatically.
484
-
485
- ## Performance Guide
486
-
487
- ### Fastest Media Settings
488
-
489
- For smooth high-density video:
490
-
491
- ```ts
492
- {
493
- fontSize: 5,
494
- fps: 60,
495
- maxRenderDimension: 1280,
496
- artStyle: 'classic',
497
- options: {
498
- charset: CHARSETS.dense,
499
- colorMode: 'accent',
500
- animationStyle: 'none',
501
- hoverStrength: 0,
502
- normalize: true,
503
- }
504
- }
505
- ```
506
-
507
- ### Fullcolor Settings
508
-
509
- Fullcolor is supported by the compact text-frame path in `1.0.91+`.
510
-
511
- ```ts
512
- {
513
- fontSize: 6,
514
- fps: 30,
515
- maxRenderDimension: 1280,
516
- artStyle: 'art',
517
- options: {
518
- charset: CHARSETS.dense,
519
- colorMode: 'fullcolor',
520
- animationStyle: 'none',
521
- hoverStrength: 0,
522
- normalize: true,
523
- }
524
- }
525
- ```
526
-
527
- Use fullcolor when the source color is important. Use `accent` when you want the highest FPS and a more designed monochrome look.
528
-
529
- ### Detail Tuning
530
-
531
- - `fontSize`: main detail/performance knob. Smaller means more cells.
532
- - `maxRenderDimension`: caps media processing resolution before ASCII conversion.
533
- - `CHARSETS.dense`: best general image/video ramp.
534
- - `CHARSETS.braille`: very high visual resolution but can be visually noisy and heavier.
535
- - `normalize: true`: improves contrast/detail for muted media.
536
- - `ditherStrength: 0-0.25`: adds texture but costs more and can shimmer in video.
537
- - `chromaKey`: useful for green/blue-screen subjects over page backgrounds.
538
-
539
- ### Expensive Features
540
-
541
- These are intentionally richer and can reduce FPS on dense canvases:
542
-
543
- - `renderMode: 'dots'`
544
- - `hoverStrength > 0`
545
- - `animationStyle !== 'none'`
546
- - `charsetFrames`
547
- - tiny `fontSize` with `fullcolor`
548
- - very high `maxRenderDimension`
549
-
550
- For interactive hover demos, use `fontSize >= 8` or smaller canvases.
551
-
552
- ## Low-Level APIs
553
-
554
- ### Compact Text Frames
555
-
556
- Use for dense static images, GIFs, videos, and fullcolor media where you do not need per-cell mutation.
557
-
558
- ```ts
559
- import {
560
- imageToAsciiTextFrame,
561
- renderTextFrameToCanvas,
562
- DEFAULT_OPTIONS,
563
- } from 'asciify-engine';
564
-
565
- const frame = imageToAsciiTextFrame(img, {
566
- ...DEFAULT_OPTIONS,
567
- fontSize: 6,
568
- colorMode: 'fullcolor',
569
- }, 1280, 720);
570
-
571
- renderTextFrameToCanvas(ctx, frame, DEFAULT_OPTIONS, 1280, 720);
572
- ```
573
-
574
- Available compact APIs:
575
-
576
- ```ts
577
- imageToAsciiTextFrame(source, options, width?, height?) -> AsciiTextFrame
578
- videoToAsciiTextFrames(video, options, width, height, fps?, maxSec?, onProgress?, startTime?)
579
- gifToAsciiTextFrames(buffer, options, width, height, onProgress?)
580
- renderTextFrameToCanvas(ctx, textFrame, options, width, height)
581
- ```
582
-
583
- `AsciiTextFrame` contains:
584
-
585
- ```ts
586
- type AsciiTextFrame = {
587
- rows: string[];
588
- cols: number;
589
- rowCount: number;
590
- colors?: Uint8ClampedArray; // present for fullcolor compact frames
591
- }
592
- ```
593
-
594
- ### Legacy Object Frames
595
-
596
- Use when you need cell objects, hover transforms, dots rendering, or compatibility:
597
-
598
- ```ts
599
- imageToAsciiFrame(source, options, width?, height?) -> { frame, cols, rows }
600
- videoToAsciiFrames(video, options, width, height, fps?, maxSec?, onProgress?, startTime?)
601
- gifToAsciiFrames(buffer, options, width, height, onProgress?)
602
- renderFrameToCanvas(ctx, frame, options, width, height, time?, mousePos?)
603
- ```
604
-
605
- ### Cache Management
606
-
607
- Long-lived editors/playgrounds that cycle many charsets or fonts can clear shared lookup caches:
608
-
609
- ```ts
610
- import { clearAsciifyCaches } from 'asciify-engine';
611
-
612
- clearAsciifyCaches();
613
- ```
614
-
615
- ## React / Next.js Pattern
616
-
617
- ```tsx
618
- 'use client';
619
-
620
- import { useEffect, useRef } from 'react';
621
- import { asciifyVideo, CHARSETS } from 'asciify-engine';
622
-
623
- export function AsciiHero() {
624
- const wrapperRef = useRef<HTMLDivElement>(null);
625
- const canvasRef = useRef<HTMLCanvasElement>(null);
626
-
627
- useEffect(() => {
628
- const wrapper = wrapperRef.current;
629
- const canvas = canvasRef.current;
630
- if (!wrapper || !canvas) return;
631
-
632
- let cleanup: (() => void) | undefined;
633
- let alive = true;
634
-
635
- asciifyVideo('/hero.mp4', canvas, {
636
- fitTo: wrapper,
637
- fontSize: 5,
638
- fps: 60,
639
- maxRenderDimension: 1280,
640
- artStyle: 'classic',
641
- options: {
642
- charset: CHARSETS.dense,
643
- colorMode: 'accent',
644
- accentColor: '#e8d7b7',
645
- normalize: true,
646
- },
647
- }).then(stop => {
648
- if (alive) cleanup = stop;
649
- else stop();
650
- });
651
-
652
- return () => {
653
- alive = false;
654
- cleanup?.();
655
- };
656
- }, []);
657
-
658
- return (
659
- <div ref={wrapperRef} style={{ position: 'relative', width: '100%', height: '100vh' }}>
660
- <canvas ref={canvasRef} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }} />
661
- </div>
662
- );
663
- }
664
- ```
665
-
666
- ## Animated Backgrounds
667
-
668
- ```ts
669
- import { asciiBackground } from 'asciify-engine';
670
-
671
- const stop = asciiBackground('#hero-bg', {
672
- type: 'aurora',
673
- colorScheme: 'auto',
674
- fontSize: 14,
675
- speed: 0.8,
676
- density: 0.55,
677
- accentColor: '#d4ff00',
678
- });
679
-
680
- stop();
681
- ```
682
-
683
- Available background types:
684
-
685
- `wave`, `rain`, `stars`, `pulse`, `noise`, `grid`, `aurora`, `silk`, `void`, `morph`, `fire`, `dna`, `terrain`, `circuit`
686
-
687
- Individual `render*Background` functions are also exported for custom canvas loops.
688
-
689
- ## Options Reference
690
-
691
- Common `AsciiOptions`:
692
-
693
- | Property | Type | Notes |
694
- |---|---|---|
695
- | `fontSize` | `number` | Main detail/performance knob. |
696
- | `charSpacing` | `number` | Cell spacing multiplier. |
697
- | `brightness` | `number` | `-1` to `1`. |
698
- | `contrast` | `number` | Positive increases contrast. |
699
- | `charset` | `string` | Use `CHARSETS`. |
700
- | `colorMode` | `'grayscale' | 'fullcolor' | 'matrix' | 'accent'` | Fullcolor is fast-path capable in `1.0.91+`. |
701
- | `accentColor` | `string` | Used for accent/matrix-style rendering. |
702
- | `invert` | `boolean | 'auto'` | Use `'auto'` for light/dark themes. |
703
- | `renderMode` | `'ascii' | 'dots'` | Dots is richer/heavier. |
704
- | `animationStyle` | `AnimationStyle` | Keep `'none'` for max FPS. |
705
- | `animationSpeed` | `number` | Animation speed multiplier. |
706
- | `dotSizeRatio` | `number` | Dot size for dots mode. |
707
- | `ditherStrength` | `number` | Use lightly for video. |
708
- | `hoverStrength` | `number` | Set `0` for fast media. |
709
- | `hoverRadius` | `number` | Hover influence radius. |
710
- | `hoverEffect` | `HoverEffect` | Spotlight/magnify/etc. |
711
- | `hoverColor` | `string` | Hover tint. |
712
- | `artStyle` | `ArtStyle` | Preset name. |
713
- | `customText` | `string` | Repeating custom text. |
714
- | `chromaKey` | `boolean | string | {r,g,b} | null` | Green/blue/custom keying. |
715
- | `chromaKeyTolerance` | `number` | Default around `60`; raise for noisy footage. |
716
- | `normalize` | `boolean` | Stretch source luminance. |
717
- | `charsetFrames` | `string[]` | Animated charset sequence; heavier. |
718
- | `charsetFps` | `number` | Charset sequence rate. |
719
-
720
- ## Charsets And Styles
721
-
722
- Use:
723
-
724
- ```ts
725
- import { CHARSETS, ART_STYLE_PRESETS } from 'asciify-engine';
726
- ```
727
-
728
- Common charsets:
729
-
730
- - `CHARSETS.standard`
731
- - `CHARSETS.dense`
732
- - `CHARSETS.blocks`
733
- - `CHARSETS.braille`
734
- - `CHARSETS.lines`
735
- - `CHARSETS.dots`
736
- - `CHARSETS.letters`
737
- - `CHARSETS.katakana`
738
- - `CHARSETS.circles`
739
- - `CHARSETS.geometric`
740
- - `CHARSETS.shadows`
741
- - `CHARSETS.starfield`
742
-
743
- Common art styles:
744
-
745
- - `classic`: standard ASCII.
746
- - `art`: dense fullcolor detail.
747
- - `particles`: dots/fullcolor.
748
- - `letters`: alphabet characters.
749
- - `terminal`: matrix green.
750
- - `braille`: high detail.
751
- - `box`, `lines`, `katakana`, `emoji`, `circles`, `shadows`, `geometric`, `waves`, `shards`, `smoke`.
752
-
753
- ## Hover And Animation
754
-
755
- Hover effects:
756
-
757
- `spotlight`, `magnify`, `repel`, `glow`, `colorShift`, `attract`, `shatter`, `trail`, `glitchText`
758
-
759
- Hover presets:
760
-
761
- `none`, `subtle`, `flashlight`, `magnifier`, `forceField`, `neon`, `fire`, `ice`, `gravity`, `shatter`, `ghost`, `glitchReveal`
762
-
763
- Animation styles:
764
-
765
- `none`, `wave`, `pulse`, `rain`, `breathe`, `sparkle`, `glitch`, `spiral`, `typewriter`, `scatter`, `waveField`, `ripple`, `melt`, `orbit`, `cellular`
766
-
767
- Best practice:
768
-
769
- - For video backgrounds and scroll heroes, use `animationStyle: 'none'` and `hoverStrength: 0`.
770
- - For interactive hover demos, keep the canvas smaller or use `fontSize >= 8`.
771
- - Avoid combining tiny fonts, fullcolor, hover, and animation unless the visual really needs it.
772
-
773
- ## Recording And Export
774
-
775
- ```ts
776
- import { createRecorder, recordAndDownload } from 'asciify-engine';
777
-
778
- await recordAndDownload(canvas, { duration: 3000, filename: 'ascii-art' });
779
-
780
- const recorder = createRecorder(canvas, { fps: 30 });
781
- recorder.start();
782
- const blob = await recorder.stop();
783
- ```
784
-
785
- ## Type Imports
786
-
787
- ```ts
788
- import type {
789
- AsciiOptions,
790
- AsciiCell,
791
- AsciiFrame,
792
- AsciiTextFrame,
793
- AsciifySimpleOptions,
794
- AsciifyVideoOptions,
795
- VideoScrollScrubOptions,
796
- ColorMode,
797
- AnimationStyle,
798
- ArtStyle,
799
- HoverEffect,
800
- } from 'asciify-engine';
801
- ```
128
+ Do not import nonexistent helpers such as `createRecorder` or `recordAndDownload` from 1.1.0. For recording, use a browser `MediaRecorder` with a supported `canvas.captureStream()` format and clean up its tracks; for snapshots, inspect the published `captureSnapshot` / `snapshotAndDownload` types.