ansimax 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,288 @@
3
3
  All notable changes to **ansimax** are documented in this file.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.3.4] — Feature additions across animations, configure, utils
7
+
8
+ Patch release adding small but useful features to several modules. No
9
+ breaking changes — every addition is opt-in.
10
+
11
+ ### Added — `animations` module
12
+
13
+ **`animate.shake(text, opts)`** — horizontal tremble effect for errors or alerts:
14
+
15
+ ```js
16
+ import { animate } from 'ansimax';
17
+
18
+ await animate.shake('Connection failed', {
19
+ times: 5,
20
+ intensity: 2,
21
+ interval: 50,
22
+ });
23
+ ```
24
+
25
+ **`animate.countUp(from, to, opts)`** — numeric animation for counters:
26
+
27
+ ```js
28
+ await animate.countUp(0, 100, {
29
+ duration: 1500,
30
+ decimals: 0,
31
+ format: (n) => `$${n.toLocaleString()}`,
32
+ easing: (t) => 1 - (1 - t) ** 3, // ease-out cubic
33
+ });
34
+ // Animates from "$0" → "$100" over 1.5 seconds
35
+ ```
36
+
37
+ Both functions support the standard animation pattern: `signal`, `reducedMotion`,
38
+ `onFrame`, `onDone`, `onAbort`.
39
+
40
+ ### Added — `configure` module
41
+
42
+ **`setConfigValue(key, value)`** — single-key shortcut:
43
+
44
+ ```js
45
+ import { setConfigValue } from 'ansimax';
46
+
47
+ setConfigValue('theme', 'dracula');
48
+ setConfigValue('animationSpeed', 'fast');
49
+ // equivalent to: configure({ theme: 'dracula' })
50
+ ```
51
+
52
+ **`subscribeConfig(listener)`** — alias for `onConfigChange` matching the
53
+ naming convention used by `themes.onChange`:
54
+
55
+ ```js
56
+ import { subscribeConfig } from 'ansimax';
57
+
58
+ const unsubscribe = subscribeConfig((newCfg, oldCfg) => {
59
+ console.log('Config changed:', newCfg);
60
+ });
61
+ ```
62
+
63
+ ### Added — `utils/ansi` module
64
+
65
+ **`hyperlink(url, label?)`** — OSC 8 escape sequence for clickable terminal links:
66
+
67
+ ```js
68
+ import { hyperlink } from 'ansimax';
69
+
70
+ console.log(`Visit ${hyperlink('https://github.com/Brashkie/ansimax', 'the repo')}`);
71
+ console.log(`Email: ${hyperlink('mailto:hi@example.com')}`);
72
+ ```
73
+
74
+ Supported terminals: VS Code, iTerm2, WezTerm, Kitty, Hyper, Alacritty, modern
75
+ Windows Terminal. Terminals without support just show the label text.
76
+
77
+ **`clearLine()`** — convenience for clearing current line + carriage return:
78
+
79
+ ```js
80
+ import { clearLine } from 'ansimax';
81
+
82
+ for (let i = 0; i <= 100; i++) {
83
+ process.stdout.write(clearLine() + `Progress: ${i}%`);
84
+ await sleep(30);
85
+ }
86
+ ```
87
+
88
+ ### Added — `utils/helpers` module
89
+
90
+ **`gradientStops(start, end, count)`** — interpolate N hex stops between two colors:
91
+
92
+ ```js
93
+ import { gradientStops } from 'ansimax';
94
+
95
+ const stops = gradientStops('#ff0000', '#0000ff', 5);
96
+ // → ['#ff0000', '#bf003f', '#7f007f', '#3f00bf', '#0000ff']
97
+ ```
98
+
99
+ **`escapeForRegex(str)`** — escape regex meta-characters in user input:
100
+
101
+ ```js
102
+ import { escapeForRegex } from 'ansimax';
103
+
104
+ const userInput = 'hello.world+code';
105
+ const re = new RegExp(escapeForRegex(userInput));
106
+ // Matches the literal string, not as a regex pattern
107
+ ```
108
+
109
+ **`measureBlock(block)`** — get dimensions of a multi-line string (ANSI-aware):
110
+
111
+ ```js
112
+ import { measureBlock, ascii } from 'ansimax';
113
+
114
+ const box = ascii.box('Hello world!');
115
+ const { width, height } = measureBlock(box);
116
+ // → { width: 15, height: 3 }
117
+ ```
118
+
119
+ ### Improved — `node-globals.d.ts`
120
+
121
+ Added ambient declarations for `AsyncIterator`, `AsyncIterable`, `AsyncGenerator`,
122
+ and `Symbol.asyncIterator`. Lets code using `for await...of` type-check without
123
+ needing `@types/node` installed at consumer projects.
124
+
125
+ ### Improved — Tests
126
+
127
+ - `+6` tests for `gradientStops`
128
+ - `+5` tests for `escapeForRegex`
129
+ - `+7` tests for `measureBlock`
130
+ - `+5` tests for `hyperlink`
131
+ - `+2` tests for `clearLine`
132
+ - `+4` tests for `setConfigValue`
133
+ - `+3` tests for `subscribeConfig`
134
+ - `+5` tests for `animate.shake`
135
+ - `+8` tests for `animate.countUp`
136
+
137
+ Total: **+45 tests** across utils, ansi, configure, animations.
138
+
139
+ ### Notes
140
+
141
+ - No runtime dependencies — still zero
142
+ - No breaking changes — drop-in replacement for `1.3.3`
143
+ - All new exports backward-compatible by default
144
+
145
+ ---
146
+
147
+ ## [1.3.3] — Feature additions to panels, json, ascii
148
+
149
+ Patch release adding new functionality to three modules. No breaking changes —
150
+ all additions are opt-in via new options/exports.
151
+
152
+ ### Added — `panels.grid(blocks, opts)`
153
+
154
+ N-column grid layout with auto-flow (reading order). Each row auto-sizes to
155
+ the tallest block; each column auto-sizes to its widest member.
156
+
157
+ ```js
158
+ import { panels, ascii } from 'ansimax';
159
+
160
+ const cards = [
161
+ ascii.box('FILES\n42', { padding: 1 }),
162
+ ascii.box('LINES\n1247', { padding: 1 }),
163
+ ascii.box('TESTS\n38', { padding: 1 }),
164
+ ascii.box('COV\n98%', { padding: 1 }),
165
+ ];
166
+
167
+ // 2×2 grid
168
+ console.log(panels.grid(cards, { columns: 2, gapX: 2, gapY: 1 }));
169
+
170
+ // 3-column with auto-flow (7 items → 3 rows: [3, 3, 1])
171
+ console.log(panels.grid(items, { columns: 3, gapX: 4 }));
172
+
173
+ // Fixed cell width for uniform appearance
174
+ console.log(panels.grid(blocks, {
175
+ columns: 4,
176
+ cellWidth: 15,
177
+ alignX: 'center',
178
+ }));
179
+ ```
180
+
181
+ Options: `columns` (required), `gapX`, `gapY`, `alignX`, `alignY`, `cellWidth`.
182
+
183
+ ### Added — `panels.frame` option `titleAlign`
184
+
185
+ Frame titles can now be aligned `'left'`, `'center'` (default), or `'right'`.
186
+
187
+ ```js
188
+ panels.frame('Body', { title: 'Section', titleAlign: 'left' });
189
+ // ─ Section ───────────
190
+ //
191
+ // Body
192
+ // ────────────────────
193
+
194
+ panels.frame('Body', { title: 'Section', titleAlign: 'right' });
195
+ // ─────────── Section ─
196
+ //
197
+ // Body
198
+ // ────────────────────
199
+ ```
200
+
201
+ ### Added — `ascii.box` options `title` + `titleAlign`
202
+
203
+ Boxes can now have a title in the top border. When the title is wider than the
204
+ content, the box expands to fit it.
205
+
206
+ ```js
207
+ console.log(ascii.box('Body content', {
208
+ title: 'Header',
209
+ titleAlign: 'left', // 'left' | 'center' (default) | 'right'
210
+ borderStyle: 'rounded',
211
+ }));
212
+
213
+ // ╭─ Header ──────╮
214
+ // │ Body content │
215
+ // ╰───────────────╯
216
+ ```
217
+
218
+ ### Added — `ascii.divider` option `align`
219
+
220
+ Divider labels can now be aligned similar to box titles.
221
+
222
+ ```js
223
+ ascii.divider({ label: 'Section', align: 'left', width: 40 });
224
+ // ─ Section ──────────────────────────────
225
+ ascii.divider({ label: 'Section', align: 'center', width: 40 });
226
+ // ─────────────── Section ────────────────
227
+ ascii.divider({ label: 'Section', align: 'right', width: 40 });
228
+ // ────────────────────────────── Section ─
229
+ ```
230
+
231
+ ### Added — `json.pretty` native types support: `Map`, `Set`, `Date`
232
+
233
+ ```js
234
+ import { json } from 'ansimax';
235
+
236
+ const data = {
237
+ created: new Date('2026-06-13'),
238
+ cache: new Map([['user1', 'Alice'], ['user2', 'Bob']]),
239
+ tags: new Set(['frontend', 'react']),
240
+ };
241
+
242
+ console.log(json.pretty(data));
243
+ // {
244
+ // "created": Date(2026-06-13T00:00:00.000Z),
245
+ // "cache": Map(2) [...],
246
+ // "tags": Set(2) ["frontend", "react"]
247
+ // }
248
+ ```
249
+
250
+ ### Added — `json.pretty` option `mode: 'json'`
251
+
252
+ Produces **strict, parseable JSON** instead of display-only output. Useful
253
+ for piping to files, scripts, or other tools.
254
+
255
+ ```js
256
+ const out = json.pretty(myData, { mode: 'json' });
257
+ const parsed = JSON.parse(out); // ✓ works
258
+ ```
259
+
260
+ In `'json'` mode:
261
+ - Colors are forced off (output is always plain text)
262
+ - `undefined` / functions / symbols are dropped from objects, become `null` in arrays
263
+ - `NaN` / `Infinity` / `-Infinity` become `null`
264
+ - `BigInt` becomes a number (if safe) or a string (if out of safe range)
265
+ - `Date` becomes its ISO string
266
+ - `Map` becomes a plain object (string-keys only)
267
+ - `Set` becomes an array
268
+ - Circular references throw `TypeError` (matches `JSON.stringify` behavior)
269
+
270
+ Default `'display'` mode preserves all v1.3.2 behavior. **No breaking changes.**
271
+
272
+ ### Improved — Tests
273
+
274
+ - `+8` tests for `panels.grid`
275
+ - `+3` tests for `panels.frame` titleAlign
276
+ - `+5` tests for `ascii.box` title/titleAlign
277
+ - `+4` tests for `ascii.divider` align
278
+ - `+18` tests for `json` Map/Set/Date/mode
279
+
280
+ ### Notes
281
+
282
+ - No runtime dependencies — still zero
283
+ - No breaking changes — drop-in replacement for `1.3.2`
284
+ - All new options have backward-compatible defaults
285
+
286
+ ---
287
+
6
288
  ## [1.3.2] — Documentation polish for frames + images
7
289
 
8
290
  Patch release improving JSDoc + IntelliSense coverage for the two largest
package/README.es.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _Colores • Gradientes • Animaciones • ASCII Art • Pixel Art • Árboles • Componentes • Temas_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.3.2-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.3.4-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-2000%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -215,7 +215,7 @@ console.log(rainbow('preset rainbow integrado'));
215
215
 
216
216
  ### Gradientes animados (v1.2.0)
217
217
 
218
- <img src="media/animated_gradients.png" alt="Vista previa de gradientes animados" />
218
+ <img src="media/animated-gradients.gif" alt="Vista previa de gradientes animados" />
219
219
 
220
220
  ```js
221
221
  import { animateGradient, sleep } from 'ansimax';
@@ -275,7 +275,7 @@ console.log(gradientRect({
275
275
 
276
276
  ### Gradientes reusables (v1.2.3)
277
277
 
278
- <img src="media/reusable_gradients.png" alt="Vista previa de gradientes reusables" />
278
+ <img src="media/reusable-gradients.gif" alt="Vista previa de gradientes reusables" />
279
279
 
280
280
  ```js
281
281
  import { createGradient, reverseGradient, ascii } from 'ansimax';
@@ -323,7 +323,32 @@ console.log(ascii.box('¡Caja arcoiris!', { padding: 1, borderStyle: 'rounded' }
323
323
 
324
324
  ### Imagen → ASCII (v1.2.5)
325
325
 
326
- <img src="media/image_to_ascii.png" alt="Vista previa de Image-to-ASCII" />
326
+ <div align="center">
327
+ <img src="media/image-ascii-original.png" alt="Foto original" width="40%" />
328
+ </div>
329
+
330
+ <table>
331
+ <tr>
332
+ <td align="center">
333
+ <img src="media/image-ascii-1.png" alt="Modo monocromo" /><br/>
334
+ <sub><b>1. Monocromo</b></sub>
335
+ </td>
336
+ <td align="center">
337
+ <img src="media/image-ascii-2.png" alt="Color + dithering Floyd-Steinberg" /><br/>
338
+ <sub><b>2. Color + Floyd-Steinberg</b></sub>
339
+ </td>
340
+ </tr>
341
+ <tr>
342
+ <td align="center">
343
+ <img src="media/image-ascii-3.png" alt="Detección de bordes (Sobel)" /><br/>
344
+ <sub><b>3. Detección de bordes (Sobel)</b></sub>
345
+ </td>
346
+ <td align="center">
347
+ <img src="media/image-ascii-4.png" alt="Modo rostro para retratos" /><br/>
348
+ <sub><b>4. Modo rostro (retratos)</b></sub>
349
+ </td>
350
+ </tr>
351
+ </table>
327
352
 
328
353
  ```js
329
354
  import { ascii } from 'ansimax';
@@ -348,10 +373,10 @@ for (let y = 0; y < info.height; y++) {
348
373
 
349
374
  // Ahora usa ansimax — varias formas:
350
375
 
351
- // Monocromo
376
+ // 1. Monocromo
352
377
  console.log(ascii.fromImage(pixels, { width: 80 }));
353
378
 
354
- // Color + dithering Floyd-Steinberg + ramp detallado
379
+ // 2. Color + dithering Floyd-Steinberg + ramp detallado
355
380
  console.log(ascii.fromImage(pixels, {
356
381
  width: 100,
357
382
  color: true,
@@ -359,7 +384,7 @@ console.log(ascii.fromImage(pixels, {
359
384
  ramp: 'detailed',
360
385
  }));
361
386
 
362
- // Modo detección de bordes (line art)
387
+ // 3. Modo detección de bordes (line art)
363
388
  console.log(ascii.fromImage(pixels, {
364
389
  width: 80,
365
390
  edgeDetect: 'sobel',
@@ -367,7 +392,7 @@ console.log(ascii.fromImage(pixels, {
367
392
  ramp: 'blocks',
368
393
  }));
369
394
 
370
- // Modo rostro para retratos (mejora contraste de tonos medios)
395
+ // 4. Modo rostro para retratos (mejora contraste de tonos medios)
371
396
  console.log(ascii.fromImage(pixels, {
372
397
  width: 60,
373
398
  ramp: 'detailed',
@@ -453,7 +478,7 @@ console.log(components.table([
453
478
  ['loaders', color.green('● listo'), '100%'],
454
479
  ], { borderStyle: 'rounded' }));
455
480
 
456
- console.log(components.badge('VERSION', 'v1.3.2'));
481
+ console.log(components.badge('VERSION', 'v1.3.4'));
457
482
  console.log(components.badge('BUILD', 'passing'));
458
483
  ```
459
484
 
@@ -505,7 +530,7 @@ await loader.tasks([
505
530
 
506
531
  ### Animaciones
507
532
 
508
- <img src="media/animations.png" alt="Vista previa de animaciones" />
533
+ <img src="media/animations-1.gif" alt="Vista previa de animaciones" />
509
534
 
510
535
  ```js
511
536
  import { animate, gradient, sleep } from 'ansimax';
@@ -615,10 +640,12 @@ console.log(json.pretty({
615
640
  }));
616
641
 
617
642
  // Límite de profundidad — colapsa objetos profundos a {...}
643
+ const deeplyNested = { a: { b: { c: { d: { e: 'muy profundo' } } } } };
618
644
  console.log(json.pretty(deeplyNested, { maxDepth: 2 }));
619
645
 
620
646
  // Límite de items — arrays grandes muestran "... (N more)"
621
- console.log(json.pretty(largeArray, { maxItems: 10 }));
647
+ const largeArray = Array.from({ length: 50 }, (_, i) => `item_${i}`);
648
+ console.log(json.pretty(largeArray, { maxItems: 5 }));
622
649
 
623
650
  // Referencias circulares manejadas con gracia
624
651
  const obj = { name: 'foo' };
@@ -1038,6 +1065,61 @@ ansimax/
1038
1065
 
1039
1066
  ## 📝 Changelog
1040
1067
 
1068
+ ### v1.3.4 — Features para animations, configure, utils
1069
+
1070
+ Release patch con features opt-in en varios módulos. Cero breaking changes:
1071
+
1072
+ - 🎬 **`animate.shake(text, opts)`** — efecto tremor horizontal para errores
1073
+ - 🔢 **`animate.countUp(from, to, opts)`** — contadores numéricos animados con format/easing
1074
+ - ⚙️ **`setConfigValue(key, value)`** — atajo single-key + alias `subscribeConfig`
1075
+ - 🔗 **`hyperlink(url, label)`** — links clickables vía OSC 8 (VS Code, iTerm2, WezTerm, Kitty...)
1076
+ - 🧹 **`clearLine()`** — helper conveniente para render loops
1077
+ - 🎨 **`gradientStops(start, end, count)`** — N stops procedurales entre dos colores
1078
+ - 🛡️ **`escapeForRegex(str)`** — escapa input de usuario para regex literals
1079
+ - 📏 **`measureBlock(block)`** — dimensiones ANSI-aware de texto multi-línea
1080
+ - 📐 **`node-globals.d.ts`** — añadidos tipos `AsyncIterator`/`AsyncIterable`/`AsyncGenerator`
1081
+ - 🧪 **+45 tests** entre animations, configure, utils
1082
+
1083
+ ```js
1084
+ import { animate, hyperlink } from 'ansimax';
1085
+
1086
+ await animate.countUp(0, 1000, {
1087
+ duration: 1500,
1088
+ format: (n) => `$${n.toLocaleString()}`,
1089
+ });
1090
+
1091
+ console.log(`Ver ${hyperlink('https://npmjs.com/ansimax', 'la página de npm')}`);
1092
+ ```
1093
+
1094
+ Drop-in replacement para `1.3.3`.
1095
+
1096
+ ### v1.3.3 — Features para panels, json, ascii
1097
+
1098
+ Release patch con nuevas features opt-in. Cero breaking changes:
1099
+
1100
+ - 📐 **`panels.grid(blocks, opts)`** — layout de grid en N columnas con auto-flow, gaps, alineación, y opción de ancho uniforme de celda
1101
+ - 🎯 **`panels.frame` + `ascii.box` + `ascii.divider`** todos reciben opción `titleAlign` (o `align`): `'left'`, `'center'` (default), `'right'`
1102
+ - 🏷️ **`ascii.box` nueva opción `title`** — muestra un label en el borde superior (expande el box si el title es más ancho que el contenido)
1103
+ - 📅 **`json.pretty` soporta Map / Set / Date nativamente** — `Date(...)`, `Map(N) [...]`, `Set(N) [...]` en modo display
1104
+ - 📤 **`json.pretty` nueva opción `mode: 'json'`** — produce JSON estricto y parseable (sin colores, descarta undefined/functions/symbols, lanza en circulares)
1105
+ - 🧪 **+38 tests** entre panels + json + ascii
1106
+
1107
+ ```js
1108
+ import { panels, json, ascii } from 'ansimax';
1109
+
1110
+ // Grid de 2×2 con metric cards
1111
+ console.log(panels.grid(cards, { columns: 2, gapX: 2 }));
1112
+
1113
+ // Title en el borde del box
1114
+ console.log(ascii.box('content', { title: 'Section', titleAlign: 'left' }));
1115
+
1116
+ // Output JSON estricto (parseable)
1117
+ const out = json.pretty(data, { mode: 'json' });
1118
+ JSON.parse(out); // ✓ funciona
1119
+ ```
1120
+
1121
+ Drop-in replacement para `1.3.2`.
1122
+
1041
1123
  ### v1.3.2 — Pulido de documentación para frames + images
1042
1124
 
1043
1125
  Release patch con cobertura JSDoc + IntelliSense significativamente mejorada:
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  _Colors • Gradients • Animations • ASCII Art • Pixel Art • Trees • Components • Themes_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.3.2-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.3.4-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
13
  [![Tests](https://img.shields.io/badge/tests-2000%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -215,7 +215,7 @@ console.log(rainbow('built-in rainbow preset'));
215
215
 
216
216
  ### Animated Gradients (v1.2.0)
217
217
 
218
- <img src="media/animated_gradients.png" alt="Animated gradients preview" />
218
+ <img src="media/animated-gradients.gif" alt="Animated gradients preview" />
219
219
 
220
220
  ```js
221
221
  import { animateGradient, sleep } from 'ansimax';
@@ -275,7 +275,7 @@ console.log(gradientRect({
275
275
 
276
276
  ### Reusable Gradients (v1.2.3)
277
277
 
278
- <img src="media/reusable_gradients.png" alt="Reusable gradients preview" />
278
+ <img src="media/reusable-gradients.gif" alt="Reusable gradients preview" />
279
279
 
280
280
  ```js
281
281
  import { createGradient, reverseGradient, ascii } from 'ansimax';
@@ -323,7 +323,32 @@ console.log(ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' }));
323
323
 
324
324
  ### Image → ASCII (v1.2.5)
325
325
 
326
- <img src="media/image_to_ascii.png" alt="Image-to-ASCII preview" />
326
+ <div align="center">
327
+ <img src="media/image-ascii-original.png" alt="Original photo" width="40%" />
328
+ </div>
329
+
330
+ <table>
331
+ <tr>
332
+ <td align="center">
333
+ <img src="media/image-ascii-1.png" alt="Monochrome mode" /><br/>
334
+ <sub><b>1. Monochrome</b></sub>
335
+ </td>
336
+ <td align="center">
337
+ <img src="media/image-ascii-2.png" alt="Color + Floyd-Steinberg dithering" /><br/>
338
+ <sub><b>2. Color + Floyd-Steinberg</b></sub>
339
+ </td>
340
+ </tr>
341
+ <tr>
342
+ <td align="center">
343
+ <img src="media/image-ascii-3.png" alt="Edge detection (Sobel)" /><br/>
344
+ <sub><b>3. Edge detection (Sobel)</b></sub>
345
+ </td>
346
+ <td align="center">
347
+ <img src="media/image-ascii-4.png" alt="Face mode for portraits" /><br/>
348
+ <sub><b>4. Face mode (portraits)</b></sub>
349
+ </td>
350
+ </tr>
351
+ </table>
327
352
 
328
353
  ```js
329
354
  import { ascii } from 'ansimax';
@@ -348,10 +373,10 @@ for (let y = 0; y < info.height; y++) {
348
373
 
349
374
  // Now use ansimax — multiple ways:
350
375
 
351
- // Monochrome
376
+ // 1. Monochrome
352
377
  console.log(ascii.fromImage(pixels, { width: 80 }));
353
378
 
354
- // Color + Floyd-Steinberg dithering + detailed ramp
379
+ // 2. Color + Floyd-Steinberg dithering + detailed ramp
355
380
  console.log(ascii.fromImage(pixels, {
356
381
  width: 100,
357
382
  color: true,
@@ -359,7 +384,7 @@ console.log(ascii.fromImage(pixels, {
359
384
  ramp: 'detailed',
360
385
  }));
361
386
 
362
- // Edge-detection mode (line art)
387
+ // 3. Edge-detection mode (line art)
363
388
  console.log(ascii.fromImage(pixels, {
364
389
  width: 80,
365
390
  edgeDetect: 'sobel',
@@ -367,7 +392,7 @@ console.log(ascii.fromImage(pixels, {
367
392
  ramp: 'blocks',
368
393
  }));
369
394
 
370
- // Face mode for portraits (boosts midtone contrast)
395
+ // 4. Face mode for portraits (boosts midtone contrast)
371
396
  console.log(ascii.fromImage(pixels, {
372
397
  width: 60,
373
398
  ramp: 'detailed',
@@ -453,7 +478,7 @@ console.log(components.table([
453
478
  ['loaders', color.green('● ready'), '100%'],
454
479
  ], { borderStyle: 'rounded' }));
455
480
 
456
- console.log(components.badge('VERSION', 'v1.3.2'));
481
+ console.log(components.badge('VERSION', 'v1.3.4'));
457
482
  console.log(components.badge('BUILD', 'passing'));
458
483
  ```
459
484
 
@@ -505,7 +530,7 @@ await loader.tasks([
505
530
 
506
531
  ### Animations
507
532
 
508
- <img src="media/animations.png" alt="Animations preview" />
533
+ <img src="media/animations-1.gif" alt="Animations preview" />
509
534
 
510
535
  ```js
511
536
  import { animate, gradient, sleep } from 'ansimax';
@@ -615,10 +640,12 @@ console.log(json.pretty({
615
640
  }));
616
641
 
617
642
  // Depth limit — collapses deep objects to {...}
643
+ const deeplyNested = { a: { b: { c: { d: { e: 'too deep' } } } } };
618
644
  console.log(json.pretty(deeplyNested, { maxDepth: 2 }));
619
645
 
620
646
  // Item limit — huge arrays show "... (N more)"
621
- console.log(json.pretty(largeArray, { maxItems: 10 }));
647
+ const largeArray = Array.from({ length: 50 }, (_, i) => `item_${i}`);
648
+ console.log(json.pretty(largeArray, { maxItems: 5 }));
622
649
 
623
650
  // Circular references handled gracefully
624
651
  const obj = { name: 'foo' };
@@ -1038,6 +1065,61 @@ ansimax/
1038
1065
 
1039
1066
  ## 📝 Changelog
1040
1067
 
1068
+ ### v1.3.4 — Feature additions across animations, configure, utils
1069
+
1070
+ Patch release with opt-in additions to several modules. Zero breaking changes:
1071
+
1072
+ - 🎬 **`animate.shake(text, opts)`** — horizontal tremble effect for error feedback
1073
+ - 🔢 **`animate.countUp(from, to, opts)`** — animated numeric counters with format/easing
1074
+ - ⚙️ **`setConfigValue(key, value)`** — single-key config shortcut + `subscribeConfig` alias
1075
+ - 🔗 **`hyperlink(url, label)`** — OSC 8 clickable terminal links (VS Code, iTerm2, WezTerm, Kitty...)
1076
+ - 🧹 **`clearLine()`** — convenience helper for render loops
1077
+ - 🎨 **`gradientStops(start, end, count)`** — procedural N-color stops
1078
+ - 🛡️ **`escapeForRegex(str)`** — escape user input for regex literals
1079
+ - 📏 **`measureBlock(block)`** — get ANSI-aware dimensions of multi-line text
1080
+ - 📐 **`node-globals.d.ts`** — added `AsyncIterator`/`AsyncIterable`/`AsyncGenerator` ambient types
1081
+ - 🧪 **+45 tests** across animations, configure, utils
1082
+
1083
+ ```js
1084
+ import { animate, hyperlink } from 'ansimax';
1085
+
1086
+ await animate.countUp(0, 1000, {
1087
+ duration: 1500,
1088
+ format: (n) => `$${n.toLocaleString()}`,
1089
+ });
1090
+
1091
+ console.log(`See ${hyperlink('https://npmjs.com/ansimax', 'the npm page')}`);
1092
+ ```
1093
+
1094
+ Drop-in replacement for `1.3.3`.
1095
+
1096
+ ### v1.3.3 — Features for panels, json, ascii
1097
+
1098
+ Patch release with new opt-in features. Zero breaking changes:
1099
+
1100
+ - 📐 **`panels.grid(blocks, opts)`** — N-column grid layout with auto-flow, gaps, alignment, and optional uniform cell width
1101
+ - 🎯 **`panels.frame` + `ascii.box` + `ascii.divider`** all get a `titleAlign` (or `align`) option: `'left'`, `'center'` (default), `'right'`
1102
+ - 🏷️ **`ascii.box` new `title` option** — show a label in the top border (expands box if title is wider than content)
1103
+ - 📅 **`json.pretty` supports Map / Set / Date natively** — `Date(...)`, `Map(N) [...]`, `Set(N) [...]` in display mode
1104
+ - 📤 **`json.pretty` new `mode: 'json'`** — produces strict, parseable JSON (no colors, drops undefined/functions/symbols, throws on circular)
1105
+ - 🧪 **+38 tests** across panels + json + ascii
1106
+
1107
+ ```js
1108
+ import { panels, json, ascii } from 'ansimax';
1109
+
1110
+ // 2×2 grid of metric cards
1111
+ console.log(panels.grid(cards, { columns: 2, gapX: 2 }));
1112
+
1113
+ // Title in box top border
1114
+ console.log(ascii.box('content', { title: 'Section', titleAlign: 'left' }));
1115
+
1116
+ // Strict JSON output (parseable)
1117
+ const out = json.pretty(data, { mode: 'json' });
1118
+ JSON.parse(out); // ✓ works
1119
+ ```
1120
+
1121
+ Drop-in replacement for `1.3.2`.
1122
+
1041
1123
  ### v1.3.2 — Documentation polish for frames + images
1042
1124
 
1043
1125
  Patch release with significantly improved JSDoc + IntelliSense coverage: