ink-motion 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ Beautiful, performant text effects and animations for [Ink](https://github.com/v
4
4
 
5
5
  ## Features
6
6
 
7
- - 🎨 **Rich text effects**: Shimmer, Typewriter, Fade, Wave
7
+ - 🎨 **Rich text effects**: Shimmer, Typewriter, Fade, Wave, Flash
8
8
  - ⚡ **Performant**: Optimised animations with minimal re-renders
9
9
  - 🔧 **Highly customisable**: Control colours, speed, intensity, and more
10
10
  - 📦 **Type-safe**: Full TypeScript support with strict types
@@ -21,9 +21,9 @@ pnpm add ink-motion
21
21
  ## Quick Start
22
22
 
23
23
  ```tsx
24
- import React from 'react'
25
24
  import { render } from 'ink'
26
25
  import { Shimmer, Typewriter } from 'ink-motion'
26
+ import React from 'react'
27
27
 
28
28
  function App() {
29
29
  return (
@@ -48,6 +48,7 @@ render(<App />)
48
48
  - **Typewriter** - Character-by-character typing animation
49
49
  - **Fade** - Smooth fade in/out transitions
50
50
  - **Wave** - Wave motion through text
51
+ - **Flash** - Pulsing neon-like glow effect
51
52
 
52
53
  ## Demo
53
54
 
package/dist/index.cjs CHANGED
@@ -1,25 +1,25 @@
1
1
  'use strict';
2
2
 
3
3
  var ink = require('ink');
4
- var React = require('react');
4
+ var react = require('react');
5
5
  var chalk = require('chalk');
6
+ var jsxRuntime = require('react/jsx-runtime');
6
7
 
7
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
9
 
9
- var React__default = /*#__PURE__*/_interopDefault(React);
10
10
  var chalk__default = /*#__PURE__*/_interopDefault(chalk);
11
11
 
12
- // src/components/Shimmer.tsx
12
+ // src/components/Fade.tsx
13
13
  var TARGET_FPS = 60;
14
14
  var FRAME_INTERVAL_MS = 1e3 / TARGET_FPS;
15
15
  function useAnimationFrame(callback, enabled = true) {
16
- const intervalRef = React.useRef();
17
- const previousTimeRef = React.useRef();
18
- const callbackRef = React.useRef(callback);
19
- React.useEffect(() => {
16
+ const intervalRef = react.useRef();
17
+ const previousTimeRef = react.useRef();
18
+ const callbackRef = react.useRef(callback);
19
+ react.useEffect(() => {
20
20
  callbackRef.current = callback;
21
21
  }, [callback]);
22
- React.useEffect(() => {
22
+ react.useEffect(() => {
23
23
  if (!enabled) {
24
24
  if (intervalRef.current) {
25
25
  clearInterval(intervalRef.current);
@@ -42,6 +42,15 @@ function useAnimationFrame(callback, enabled = true) {
42
42
  };
43
43
  }, [enabled]);
44
44
  }
45
+
46
+ // src/hooks/useElapsedTime.ts
47
+ function useElapsedTime(enabled = true, speed = 1) {
48
+ const [elapsedTime, setElapsedTime] = react.useState(0);
49
+ useAnimationFrame((deltaTime) => {
50
+ setElapsedTime((prev) => prev + deltaTime * speed);
51
+ }, enabled);
52
+ return elapsedTime;
53
+ }
45
54
  var HEX_BASE = 16;
46
55
  var DECIMAL_BASE = 10;
47
56
  var HEX_BYTE_LENGTH = 2;
@@ -139,6 +148,87 @@ function getEasingFunction(name = "linear") {
139
148
  function clamp(value, min, max) {
140
149
  return Math.min(Math.max(value, min), max);
141
150
  }
151
+ var DEFAULT_FROM = 0;
152
+ var DEFAULT_TO = 1;
153
+ var DEFAULT_DURATION_MS = 1e3;
154
+ var DEFAULT_EASING = "ease-out";
155
+ var DEFAULT_LOOP = false;
156
+ var DEFAULT_SPEED = 1;
157
+ function calculateOpacity(elapsedTime, duration, from, to, easing) {
158
+ const progress = Math.min(elapsedTime / duration, 1);
159
+ const easedProgress = getEasingFunction(easing)(progress);
160
+ return from + (to - from) * easedProgress;
161
+ }
162
+ function Fade({
163
+ children,
164
+ color,
165
+ from = DEFAULT_FROM,
166
+ to = DEFAULT_TO,
167
+ duration = DEFAULT_DURATION_MS,
168
+ easing = DEFAULT_EASING,
169
+ loop = DEFAULT_LOOP,
170
+ speed = DEFAULT_SPEED,
171
+ enabled = true,
172
+ onComplete
173
+ }) {
174
+ const [hasCompleted, setHasCompleted] = react.useState(false);
175
+ const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed);
176
+ react.useEffect(() => {
177
+ if (!enabled)
178
+ return;
179
+ const isComplete = elapsedTime >= duration;
180
+ if (isComplete && !loop && !hasCompleted) {
181
+ setHasCompleted(true);
182
+ onComplete?.();
183
+ }
184
+ }, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete]);
185
+ const fadedText = react.useMemo(() => {
186
+ const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration);
187
+ const opacity = calculateOpacity(effectiveTime, duration, from, to, easing);
188
+ const baseColor = color ?? "#ffffff";
189
+ const fadedColor = applyOpacity(baseColor, opacity);
190
+ return colorize(children, fadedColor);
191
+ }, [children, elapsedTime, duration, from, to, easing, color, loop]);
192
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: fadedText });
193
+ }
194
+ var DEFAULT_COLOR = "#ffffff";
195
+ var DEFAULT_MIN_INTENSITY = 0.3;
196
+ var DEFAULT_MAX_INTENSITY = 1;
197
+ var DEFAULT_DURATION_MS2 = 1e3;
198
+ var DEFAULT_SPEED2 = 1;
199
+ var FULL_CIRCLE_RADIANS = Math.PI * 2;
200
+ var SINE_WAVE_OFFSET = 1;
201
+ var SINE_WAVE_NORMALIZE = 0.5;
202
+ var MIN_INTENSITY = 0;
203
+ var MAX_INTENSITY = 1;
204
+ function calculateFlashIntensity(time, duration, minIntensity, maxIntensity) {
205
+ const clampedMin = clamp(minIntensity, MIN_INTENSITY, MAX_INTENSITY);
206
+ const clampedMax = clamp(maxIntensity, MIN_INTENSITY, MAX_INTENSITY);
207
+ const normalizedTime = time / duration;
208
+ const phase = normalizedTime * FULL_CIRCLE_RADIANS;
209
+ const sineWave = Math.sin(phase);
210
+ const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE;
211
+ const intensityRange = clampedMax - clampedMin;
212
+ return clampedMin + normalizedSine * intensityRange;
213
+ }
214
+ function Flash({
215
+ children,
216
+ color = DEFAULT_COLOR,
217
+ minIntensity = DEFAULT_MIN_INTENSITY,
218
+ maxIntensity = DEFAULT_MAX_INTENSITY,
219
+ duration = DEFAULT_DURATION_MS2,
220
+ speed = DEFAULT_SPEED2,
221
+ enabled = true
222
+ }) {
223
+ const elapsedTime = useElapsedTime(enabled, speed);
224
+ const cycleTime = elapsedTime % duration;
225
+ const flashText = react.useMemo(() => {
226
+ const intensity = calculateFlashIntensity(cycleTime, duration, minIntensity, maxIntensity);
227
+ const adjustedColor = applyOpacity(color, intensity);
228
+ return colorize(children, adjustedColor);
229
+ }, [children, cycleTime, duration, minIntensity, maxIntensity, color]);
230
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: flashText });
231
+ }
142
232
 
143
233
  // src/utils/text.ts
144
234
  function splitChars(text) {
@@ -150,29 +240,27 @@ function getTextLength(text) {
150
240
  function mapChars(text, fn) {
151
241
  return splitChars(text).map(fn).join("");
152
242
  }
153
-
154
- // src/components/Shimmer.tsx
155
243
  var DEFAULT_COLORS = ["#666666", "#ffffff", "#666666"];
156
244
  var DEFAULT_WIDTH = 4;
157
245
  var DEFAULT_INTENSITY = 1;
158
246
  var DEFAULT_DIRECTION = "right";
159
- var DEFAULT_SPEED = 1;
247
+ var DEFAULT_SPEED3 = 1;
160
248
  var ANIMATION_CYCLE_MS = 2e3;
161
249
  var COLOUR_TRANSITION_MIDPOINT = 0.5;
162
250
  var COLOUR_PROGRESS_MULTIPLIER = 2;
163
- var MIN_INTENSITY = 0;
164
- var MAX_INTENSITY = 1;
251
+ var MIN_INTENSITY2 = 0;
252
+ var MAX_INTENSITY2 = 1;
165
253
  function Shimmer({
166
254
  children,
167
255
  colors = DEFAULT_COLORS,
168
256
  width = DEFAULT_WIDTH,
169
257
  intensity = DEFAULT_INTENSITY,
170
258
  direction = DEFAULT_DIRECTION,
171
- speed = DEFAULT_SPEED,
259
+ speed = DEFAULT_SPEED3,
172
260
  enabled = true,
173
261
  onComplete
174
262
  }) {
175
- const [offset, setOffset] = React.useState(0);
263
+ const [offset, setOffset] = react.useState(0);
176
264
  const textLength = getTextLength(children);
177
265
  const totalWidth = textLength + width;
178
266
  useAnimationFrame((deltaTime) => {
@@ -187,14 +275,14 @@ function Shimmer({
187
275
  return newOffset;
188
276
  });
189
277
  }, enabled);
190
- const shimmerText = React.useMemo(() => {
278
+ const shimmerText = react.useMemo(() => {
191
279
  return mapChars(children, (char, index) => {
192
280
  const normalizedOffset = direction === "right" ? offset : totalWidth - offset;
193
281
  const distance = Math.abs(index - normalizedOffset);
194
282
  const isWithinShimmerWidth = distance < width;
195
283
  const colorProgress = isWithinShimmerWidth ? distance / width : 1;
196
284
  const [startColor, peakColor, endColor] = colors;
197
- const adjustedIntensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY);
285
+ const adjustedIntensity = clamp(intensity, MIN_INTENSITY2, MAX_INTENSITY2);
198
286
  const isFirstHalf = colorProgress < COLOUR_TRANSITION_MIDPOINT;
199
287
  const currentColor = isFirstHalf ? interpolateColor(
200
288
  startColor,
@@ -208,16 +296,16 @@ function Shimmer({
208
296
  return colorize(char, currentColor);
209
297
  });
210
298
  }, [children, offset, colors, width, intensity, direction, totalWidth]);
211
- return /* @__PURE__ */ React__default.default.createElement(ink.Text, null, shimmerText);
299
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: shimmerText });
212
300
  }
213
301
  var CURSOR_BLINK_INTERVAL_MS = 500;
214
302
  function useCursorBlink({
215
303
  enabled,
216
304
  isComplete
217
305
  }) {
218
- const [showCursor, setShowCursor] = React.useState(true);
219
- const intervalRef = React.useRef();
220
- React.useEffect(() => {
306
+ const [showCursor, setShowCursor] = react.useState(true);
307
+ const intervalRef = react.useRef();
308
+ react.useEffect(() => {
221
309
  if (!enabled || isComplete) {
222
310
  setShowCursor(false);
223
311
  return;
@@ -249,10 +337,10 @@ function useTypewriterProgress({
249
337
  enabled,
250
338
  onComplete
251
339
  }) {
252
- const [visibleCharacters, setVisibleCharacters] = React.useState(0);
253
- const timeoutRef = React.useRef();
340
+ const [visibleCharacters, setVisibleCharacters] = react.useState(0);
341
+ const timeoutRef = react.useRef();
254
342
  const hasCompletedTyping = visibleCharacters >= totalCharacters;
255
- React.useEffect(() => {
343
+ react.useEffect(() => {
256
344
  if (!enabled)
257
345
  return;
258
346
  if (hasCompletedTyping) {
@@ -272,12 +360,10 @@ function useTypewriterProgress({
272
360
  }, [visibleCharacters, enabled, speed, variance, initialDelay, hasCompletedTyping, onComplete]);
273
361
  return visibleCharacters;
274
362
  }
275
-
276
- // src/components/Typewriter.tsx
277
363
  var DEFAULT_CURSOR = "\u258B";
278
364
  var DEFAULT_VARIANCE = 0.3;
279
365
  var DEFAULT_DELAY = 0;
280
- var DEFAULT_SPEED2 = 1;
366
+ var DEFAULT_SPEED4 = 1;
281
367
  var FIRST_CHARACTER_INDEX2 = 0;
282
368
  function getCursorCharacter(cursor) {
283
369
  return typeof cursor === "string" ? cursor : DEFAULT_CURSOR;
@@ -296,7 +382,7 @@ function Typewriter({
296
382
  cursorColor,
297
383
  variance = DEFAULT_VARIANCE,
298
384
  delay = DEFAULT_DELAY,
299
- speed = DEFAULT_SPEED2,
385
+ speed = DEFAULT_SPEED4,
300
386
  enabled = true,
301
387
  onComplete
302
388
  }) {
@@ -316,7 +402,7 @@ function Typewriter({
316
402
  enabled: enabled && isCursorEnabled,
317
403
  isComplete: hasCompletedTyping
318
404
  });
319
- const displayText = React.useMemo(() => {
405
+ const displayText = react.useMemo(() => {
320
406
  const visibleText = characters.slice(FIRST_CHARACTER_INDEX2, visibleCharacters).join("");
321
407
  const coloredText = color ? colorize(visibleText, color) : visibleText;
322
408
  const shouldShowCursor = isCursorEnabled && showCursor && !hasCompletedTyping;
@@ -326,81 +412,29 @@ function Typewriter({
326
412
  const cursorText = formatCursor(cursorCharacter, cursorColor, color);
327
413
  return coloredText + cursorText;
328
414
  }, [characters, visibleCharacters, color, cursor, cursorColor, showCursor, hasCompletedTyping, isCursorEnabled]);
329
- return /* @__PURE__ */ React__default.default.createElement(ink.Text, null, displayText);
330
- }
331
- function useElapsedTime(enabled = true, speed = 1) {
332
- const [elapsedTime, setElapsedTime] = React.useState(0);
333
- useAnimationFrame((deltaTime) => {
334
- setElapsedTime((prev) => prev + deltaTime * speed);
335
- }, enabled);
336
- return elapsedTime;
337
- }
338
-
339
- // src/components/Fade.tsx
340
- var DEFAULT_FROM = 0;
341
- var DEFAULT_TO = 1;
342
- var DEFAULT_DURATION_MS = 1e3;
343
- var DEFAULT_EASING = "ease-out";
344
- var DEFAULT_LOOP = false;
345
- var DEFAULT_SPEED3 = 1;
346
- function calculateOpacity(elapsedTime, duration, from, to, easing) {
347
- const progress = Math.min(elapsedTime / duration, 1);
348
- const easedProgress = getEasingFunction(easing)(progress);
349
- return from + (to - from) * easedProgress;
350
- }
351
- function Fade({
352
- children,
353
- color,
354
- from = DEFAULT_FROM,
355
- to = DEFAULT_TO,
356
- duration = DEFAULT_DURATION_MS,
357
- easing = DEFAULT_EASING,
358
- loop = DEFAULT_LOOP,
359
- speed = DEFAULT_SPEED3,
360
- enabled = true,
361
- onComplete
362
- }) {
363
- const [hasCompleted, setHasCompleted] = React.useState(false);
364
- const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed);
365
- React.useEffect(() => {
366
- if (!enabled)
367
- return;
368
- const isComplete = elapsedTime >= duration;
369
- if (isComplete && !loop && !hasCompleted) {
370
- setHasCompleted(true);
371
- onComplete?.();
372
- }
373
- }, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete]);
374
- const fadedText = React.useMemo(() => {
375
- const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration);
376
- const opacity = calculateOpacity(effectiveTime, duration, from, to, easing);
377
- const baseColor = color ?? "#ffffff";
378
- const fadedColor = applyOpacity(baseColor, opacity);
379
- return colorize(children, fadedColor);
380
- }, [children, elapsedTime, duration, from, to, easing, color, loop]);
381
- return /* @__PURE__ */ React__default.default.createElement(ink.Text, null, fadedText);
415
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: displayText });
382
416
  }
383
417
  var DEFAULT_COLORS2 = ["#888888", "#ffffff"];
384
418
  var DEFAULT_AMPLITUDE = 0.5;
385
419
  var DEFAULT_FREQUENCY = 2;
386
420
  var DEFAULT_TYPE = "brightness";
387
- var DEFAULT_SPEED4 = 1;
388
- var FULL_CIRCLE_RADIANS = Math.PI * 2;
421
+ var DEFAULT_SPEED5 = 1;
422
+ var FULL_CIRCLE_RADIANS2 = Math.PI * 2;
389
423
  var WAVE_PERIOD_MS = 2e3;
390
424
  var MIN_AMPLITUDE = 0;
391
425
  var MAX_AMPLITUDE = 1;
392
- var SINE_WAVE_OFFSET = 1;
393
- var SINE_WAVE_NORMALIZE = 0.5;
426
+ var SINE_WAVE_OFFSET2 = 1;
427
+ var SINE_WAVE_NORMALIZE2 = 0.5;
394
428
  var VERTICAL_SHIFT_THRESHOLD = 0.5;
395
429
  function calculateWaveValue(characterIndex, time, frequency, amplitude) {
396
430
  const clampedAmplitude = clamp(amplitude, MIN_AMPLITUDE, MAX_AMPLITUDE);
397
431
  const safeFrequency = frequency || DEFAULT_FREQUENCY;
398
- const waveLength = FULL_CIRCLE_RADIANS / safeFrequency;
399
- const spatialComponent = characterIndex / waveLength * FULL_CIRCLE_RADIANS;
400
- const temporalComponent = time * FULL_CIRCLE_RADIANS;
432
+ const waveLength = FULL_CIRCLE_RADIANS2 / safeFrequency;
433
+ const spatialComponent = characterIndex / waveLength * FULL_CIRCLE_RADIANS2;
434
+ const temporalComponent = time * FULL_CIRCLE_RADIANS2;
401
435
  const phase = spatialComponent - temporalComponent;
402
436
  const sineWave = Math.sin(phase);
403
- const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE;
437
+ const normalizedSine = (sineWave + SINE_WAVE_OFFSET2) * SINE_WAVE_NORMALIZE2;
404
438
  const amplitudeComplement = MAX_AMPLITUDE - clampedAmplitude;
405
439
  return normalizedSine * clampedAmplitude + amplitudeComplement;
406
440
  }
@@ -410,12 +444,12 @@ function Wave({
410
444
  amplitude = DEFAULT_AMPLITUDE,
411
445
  frequency = DEFAULT_FREQUENCY,
412
446
  type = DEFAULT_TYPE,
413
- speed = DEFAULT_SPEED4,
447
+ speed = DEFAULT_SPEED5,
414
448
  enabled = true
415
449
  }) {
416
450
  const elapsedTime = useElapsedTime(enabled, speed);
417
451
  const normalizedTime = elapsedTime / WAVE_PERIOD_MS;
418
- const waveText = React.useMemo(() => {
452
+ const waveText = react.useMemo(() => {
419
453
  if (type === "brightness") {
420
454
  return mapChars(children, (character, index) => {
421
455
  const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude);
@@ -430,10 +464,11 @@ function Wave({
430
464
  return shouldShift ? ` ${character}` : character;
431
465
  });
432
466
  }, [children, normalizedTime, colors, amplitude, frequency, type]);
433
- return /* @__PURE__ */ React__default.default.createElement(ink.Text, null, waveText);
467
+ return /* @__PURE__ */ jsxRuntime.jsx(ink.Text, { children: waveText });
434
468
  }
435
469
 
436
470
  exports.Fade = Fade;
471
+ exports.Flash = Flash;
437
472
  exports.Shimmer = Shimmer;
438
473
  exports.Typewriter = Typewriter;
439
474
  exports.Wave = Wave;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useAnimationFrame.ts","../src/utils/colors.ts","../src/utils/easing.ts","../src/utils/text.ts","../src/components/Shimmer.tsx","../src/hooks/useCursorBlink.ts","../src/hooks/useTypewriterProgress.ts","../src/components/Typewriter.tsx","../src/hooks/useElapsedTime.ts","../src/components/Fade.tsx","../src/components/Wave.tsx"],"names":["useRef","useEffect","chalk","useState","useMemo","React","Text","DEFAULT_SPEED","FIRST_CHARACTER_INDEX","DEFAULT_COLORS"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useEffect, useRef } from 'react'\n\nconst TARGET_FPS = 60\nconst FRAME_INTERVAL_MS = 1000 / TARGET_FPS\n\n/**\n * Runs a callback on every animation frame using setInterval\n *\n * @param callback - Function called each frame with delta time in ms\n * @param enabled - Whether animation is active\n */\nexport function useAnimationFrame(\n callback: (deltaTime: number) => void,\n enabled: boolean = true,\n): void {\n const intervalRef = useRef<Timer>()\n const previousTimeRef = useRef<number>()\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n if (!enabled) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n previousTimeRef.current = undefined\n return\n }\n\n intervalRef.current = setInterval(() => {\n const currentTime = Date.now()\n if (previousTimeRef.current !== undefined) {\n const deltaTime = currentTime - previousTimeRef.current\n callbackRef.current(deltaTime)\n }\n previousTimeRef.current = currentTime\n }, FRAME_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n }\n }, [enabled])\n}\n","import chalk from 'chalk'\nimport type { Colour } from '../types/index.js'\n\nconst HEX_BASE = 16\nconst DECIMAL_BASE = 10\nconst HEX_BYTE_LENGTH = 2\nconst HEX_PAD_CHARACTER = '0'\nconst MIN_RGB_COMPONENTS = 3\nconst INTERPOLATION_MIDPOINT = 0.5\n\nconst HEX_COLOR_REGEX = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i\nconst RGB_NUMBERS_REGEX = /\\d+/g\n\nfunction rgbToHex(red: number, green: number, blue: number): string {\n const redHex = red.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const greenHex = green.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const blueHex = blue.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n return `#${redHex}${greenHex}${blueHex}`\n}\n\n/**\n * Converts a hex color string to RGB values\n *\n * @param hex - Hex color string (with or without #)\n * @returns Tuple of [red, green, blue] values (0-255) or null if invalid\n */\nexport function hexToRgb(hex: string): [number, number, number] | null {\n const result = HEX_COLOR_REGEX.exec(hex)\n if (!result)\n return null\n\n const [, redHex, greenHex, blueHex] = result\n return [\n Number.parseInt(redHex, HEX_BASE),\n Number.parseInt(greenHex, HEX_BASE),\n Number.parseInt(blueHex, HEX_BASE),\n ]\n}\n\n/**\n * Applies color to text using chalk\n *\n * @param text - Text to colorize\n * @param color - Color as hex (#ff0000), rgb(255,0,0), or named color (red, blue, etc)\n * @returns Colorized text with ANSI escape codes\n */\nexport function colorize(text: string, color: Colour): string {\n try {\n if (color.startsWith('#'))\n return chalk.hex(color)(text)\n\n if (color.startsWith('rgb')) {\n const match = color.match(RGB_NUMBERS_REGEX)\n if (!match || match.length < MIN_RGB_COMPONENTS)\n return text\n\n const [redStr, greenStr, blueStr] = match\n const red = Number.parseInt(redStr, DECIMAL_BASE)\n const green = Number.parseInt(greenStr, DECIMAL_BASE)\n const blue = Number.parseInt(blueStr, DECIMAL_BASE)\n\n if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue))\n return text\n\n return chalk.rgb(red, green, blue)(text)\n }\n\n const chalkColor = chalk[color as keyof typeof chalk]\n if (typeof chalkColor === 'function')\n return chalkColor(text)\n\n return text\n }\n catch {\n return text\n }\n}\n\n/**\n * Applies opacity to a hex color by darkening it\n * Note: Terminal approximation - true opacity not possible in most terminals\n *\n * @param color - Hex color string\n * @param opacity - Opacity value from 0 (transparent/black) to 1 (fully opaque)\n * @returns Adjusted hex color or original color if not hex format\n */\nexport function applyOpacity(color: Colour, opacity: number): Colour {\n if (!color.startsWith('#'))\n return color\n\n const rgb = hexToRgb(color)\n if (!rgb)\n return color\n\n const [red, green, blue] = rgb\n const adjustedRed = Math.round(red * opacity)\n const adjustedGreen = Math.round(green * opacity)\n const adjustedBlue = Math.round(blue * opacity)\n\n return rgbToHex(adjustedRed, adjustedGreen, adjustedBlue)\n}\n\n/**\n * Interpolates between two colors\n *\n * @param color1 - Starting color\n * @param color2 - Ending color\n * @param progress - Interpolation progress from 0 to 1\n * @returns Interpolated color (hex if both inputs are hex, otherwise snaps to nearest)\n */\nexport function interpolateColor(\n color1: Colour,\n color2: Colour,\n progress: number,\n): Colour {\n const isColor1Hex = color1.startsWith('#')\n const isColor2Hex = color2.startsWith('#')\n\n if (!isColor1Hex || !isColor2Hex)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const rgb1 = hexToRgb(color1)\n const rgb2 = hexToRgb(color2)\n\n if (!rgb1 || !rgb2)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const [red1, green1, blue1] = rgb1\n const [red2, green2, blue2] = rgb2\n\n const red = Math.round(red1 + (red2 - red1) * progress)\n const green = Math.round(green1 + (green2 - green1) * progress)\n const blue = Math.round(blue1 + (blue2 - blue1) * progress)\n\n return rgbToHex(red, green, blue)\n}\n","import type { EasingFunction, EasingName } from '../types/index.js'\n\nexport const linear: EasingFunction = time => time\n\nexport const easeIn: EasingFunction = time => time * time\n\nexport const easeOut: EasingFunction = time => time * (2 - time)\n\nexport const easeInOut: EasingFunction = (time) => {\n return time < 0.5\n ? 2 * time * time\n : -1 + (4 - 2 * time) * time\n}\n\nexport const easingFunctions: Record<EasingName, EasingFunction> = {\n 'linear': linear,\n 'ease-in': easeIn,\n 'ease-out': easeOut,\n 'ease-in-out': easeInOut,\n}\n\nexport function getEasingFunction(name: EasingName = 'linear'): EasingFunction {\n return easingFunctions[name]\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function normalize(value: number, min: number, max: number): number {\n return clamp((value - min) / (max - min), 0, 1)\n}\n\nexport function lerp(start: number, end: number, progress: number): number {\n return start + (end - start) * progress\n}\n","export function splitChars(text: string): string[] {\n return [...text]\n}\n\nexport function getTextLength(text: string): number {\n return [...text].length\n}\n\nexport function mapChars(\n text: string,\n fn: (char: string, index: number) => string,\n): string {\n return splitChars(text).map(fn).join('')\n}\n","import { Text } from 'ink'\nimport React, { useMemo, useState } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useAnimationFrame } from '../hooks/useAnimationFrame.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { getTextLength, mapChars } from '../utils/text.js'\n\ntype ShimmerDirection = 'left' | 'right'\n\ninterface ShimmerProps extends BaseEffectProps {\n /**\n * Gradient colours for the shimmer effect [start, peak, end]\n * @default ['#666666', '#ffffff', '#666666']\n * @example ['#60a5fa', '#3b82f6', '#60a5fa']\n */\n colors?: [Colour, Colour, Colour]\n\n /**\n * Width of the shimmer band in characters\n * @default 4\n */\n width?: number\n\n /**\n * Brightness multiplier (0-1)\n * @default 1\n */\n intensity?: number\n\n /**\n * Direction of shimmer movement\n * @default 'right'\n */\n direction?: ShimmerDirection\n}\n\nconst DEFAULT_COLORS: [Colour, Colour, Colour] = ['#666666', '#ffffff', '#666666']\nconst DEFAULT_WIDTH = 4\nconst DEFAULT_INTENSITY = 1\nconst DEFAULT_DIRECTION: ShimmerDirection = 'right'\nconst DEFAULT_SPEED = 1\nconst ANIMATION_CYCLE_MS = 2000\nconst COLOUR_TRANSITION_MIDPOINT = 0.5\nconst COLOUR_PROGRESS_MULTIPLIER = 2\nconst MIN_INTENSITY = 0\nconst MAX_INTENSITY = 1\n\n/**\n * Shimmer effect component that creates a moving highlight across text\n *\n * Creates a shimmering animation by interpolating through a gradient of colours\n * that sweeps across the text from left to right or right to left.\n *\n * @example\n * ```tsx\n * <Shimmer colors={['#60a5fa', '#3b82f6', '#60a5fa']} intensity={0.8}>\n * Loading...\n * </Shimmer>\n * ```\n */\nexport function Shimmer({\n children,\n colors = DEFAULT_COLORS,\n width = DEFAULT_WIDTH,\n intensity = DEFAULT_INTENSITY,\n direction = DEFAULT_DIRECTION,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: ShimmerProps) {\n const [offset, setOffset] = useState(0)\n const textLength = getTextLength(children)\n const totalWidth = textLength + width\n\n useAnimationFrame((deltaTime) => {\n setOffset((previousOffset) => {\n const movement = (deltaTime / ANIMATION_CYCLE_MS) * speed * totalWidth\n const newOffset = direction === 'right'\n ? previousOffset + movement\n : previousOffset - movement\n\n const hasCompletedCycle = Math.abs(newOffset) >= totalWidth\n if (hasCompletedCycle) {\n onComplete?.()\n return 0\n }\n\n return newOffset\n })\n }, enabled)\n\n const shimmerText = useMemo(() => {\n return mapChars(children, (char, index) => {\n const normalizedOffset = direction === 'right' ? offset : totalWidth - offset\n const distance = Math.abs(index - normalizedOffset)\n\n const isWithinShimmerWidth = distance < width\n const colorProgress = isWithinShimmerWidth\n ? distance / width\n : 1\n\n const [startColor, peakColor, endColor] = colors\n const adjustedIntensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY)\n\n const isFirstHalf = colorProgress < COLOUR_TRANSITION_MIDPOINT\n const currentColor = isFirstHalf\n ? interpolateColor(\n startColor,\n peakColor,\n colorProgress * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n : interpolateColor(\n peakColor,\n endColor,\n (colorProgress - COLOUR_TRANSITION_MIDPOINT) * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n\n return colorize(char, currentColor)\n })\n }, [children, offset, colors, width, intensity, direction, totalWidth])\n\n return <Text>{shimmerText}</Text>\n}\n","import { useEffect, useRef, useState } from 'react'\n\nconst CURSOR_BLINK_INTERVAL_MS = 500\n\ninterface UseCursorBlinkOptions {\n enabled: boolean\n isComplete: boolean\n}\n\n/**\n * Hook that manages cursor blinking state\n *\n * @param options - Configuration for cursor blink\n * @returns Whether cursor should be visible\n */\nexport function useCursorBlink({\n enabled,\n isComplete,\n}: UseCursorBlinkOptions): boolean {\n const [showCursor, setShowCursor] = useState(true)\n const intervalRef = useRef<Timer>()\n\n useEffect(() => {\n if (!enabled || isComplete) {\n setShowCursor(false)\n return\n }\n\n intervalRef.current = setInterval(() => {\n setShowCursor(previous => !previous)\n }, CURSOR_BLINK_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current)\n clearInterval(intervalRef.current)\n }\n }, [enabled, isComplete])\n\n return showCursor\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { clamp } from '../utils/easing.js'\n\nconst BASE_CHARACTER_DELAY_MS = 80\nconst MIN_VARIANCE = 0\nconst MAX_VARIANCE = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction calculateCharacterDelay(\n speed: number,\n variance: number,\n): number {\n const clampedVariance = clamp(variance, MIN_VARIANCE, MAX_VARIANCE)\n const randomVariance = (Math.random() - 0.5) * clampedVariance\n return (BASE_CHARACTER_DELAY_MS / speed) * (1 + randomVariance)\n}\n\ninterface UseTypewriterProgressOptions {\n totalCharacters: number\n speed: number\n variance: number\n initialDelay: number\n enabled: boolean\n onComplete?: () => void\n}\n\n/**\n * Hook that manages typewriter character reveal timing\n *\n * @param options - Configuration for typewriter progress\n * @returns Number of characters to display\n */\nexport function useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay,\n enabled,\n onComplete,\n}: UseTypewriterProgressOptions): number {\n const [visibleCharacters, setVisibleCharacters] = useState(0)\n const timeoutRef = useRef<Timer>()\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n\n useEffect(() => {\n if (!enabled)\n return\n\n if (hasCompletedTyping) {\n onComplete?.()\n return\n }\n\n const characterDelay = calculateCharacterDelay(speed, variance)\n const isFirstCharacter = visibleCharacters === FIRST_CHARACTER_INDEX\n const totalDelay = isFirstCharacter ? initialDelay + characterDelay : characterDelay\n\n timeoutRef.current = setTimeout(() => {\n setVisibleCharacters(previous => previous + 1)\n }, totalDelay)\n\n return () => {\n if (timeoutRef.current)\n clearTimeout(timeoutRef.current)\n }\n }, [visibleCharacters, enabled, speed, variance, initialDelay, hasCompletedTyping, onComplete])\n\n return visibleCharacters\n}\n","import { Text } from 'ink'\nimport React, { useMemo } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useCursorBlink } from '../hooks/useCursorBlink.js'\nimport { useTypewriterProgress } from '../hooks/useTypewriterProgress.js'\nimport { colorize } from '../utils/colors.js'\nimport { getTextLength, splitChars } from '../utils/text.js'\n\ninterface TypewriterProps extends BaseEffectProps {\n /**\n * Text colour\n * @default undefined (inherits)\n * @example 'green'\n */\n color?: Colour\n\n /**\n * Cursor character or false to disable\n * @default '▋'\n * @example '█'\n */\n cursor?: string | boolean\n\n /**\n * Cursor colour (defaults to text colour)\n * @default undefined\n * @example 'cyan'\n */\n cursorColor?: Colour\n\n /**\n * Typing speed randomness (0-1) for more human-like typing\n * @default 0.3\n */\n variance?: number\n\n /**\n * Initial delay before typing starts (ms)\n * @default 0\n */\n delay?: number\n}\n\nconst DEFAULT_CURSOR = '▋'\nconst DEFAULT_VARIANCE = 0.3\nconst DEFAULT_DELAY = 0\nconst DEFAULT_SPEED = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction getCursorCharacter(cursor: string | boolean): string {\n return typeof cursor === 'string' ? cursor : DEFAULT_CURSOR\n}\n\nfunction formatCursor(\n cursorCharacter: string,\n cursorColor: Colour | undefined,\n textColor: Colour | undefined,\n): string {\n if (cursorColor)\n return colorize(cursorCharacter, cursorColor)\n\n if (textColor)\n return colorize(cursorCharacter, textColor)\n\n return cursorCharacter\n}\n\n/**\n * Typewriter effect component that reveals text character by character\n *\n * Simulates typing text with configurable speed, variance for realistic timing,\n * and an optional cursor. The cursor blinks while typing is in progress.\n *\n * @example\n * ```tsx\n * <Typewriter color=\"green\" cursor=\"█\" variance={0.5} speed={2}>\n * npm install ink-motion\n * </Typewriter>\n * ```\n */\nexport function Typewriter({\n children,\n color,\n cursor = DEFAULT_CURSOR,\n cursorColor,\n variance = DEFAULT_VARIANCE,\n delay = DEFAULT_DELAY,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: TypewriterProps) {\n const characters = splitChars(children)\n const totalCharacters = getTextLength(children)\n\n const visibleCharacters = useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay: delay,\n enabled,\n onComplete,\n })\n\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n const isCursorEnabled = cursor !== false\n\n const showCursor = useCursorBlink({\n enabled: enabled && isCursorEnabled,\n isComplete: hasCompletedTyping,\n })\n\n const displayText = useMemo(() => {\n const visibleText = characters.slice(FIRST_CHARACTER_INDEX, visibleCharacters).join('')\n const coloredText = color ? colorize(visibleText, color) : visibleText\n\n const shouldShowCursor = isCursorEnabled && showCursor && !hasCompletedTyping\n if (!shouldShowCursor)\n return coloredText\n\n const cursorCharacter = getCursorCharacter(cursor)\n const cursorText = formatCursor(cursorCharacter, cursorColor, color)\n\n return coloredText + cursorText\n }, [characters, visibleCharacters, color, cursor, cursorColor, showCursor, hasCompletedTyping, isCursorEnabled])\n\n return <Text>{displayText}</Text>\n}\n","import { useState } from 'react'\nimport { useAnimationFrame } from './useAnimationFrame.js'\n\n/**\n * Hook that tracks elapsed time since mount or last reset\n *\n * @param enabled - Whether to track time\n * @param speed - Speed multiplier (default: 1)\n * @returns Elapsed time in milliseconds\n */\nexport function useElapsedTime(\n enabled: boolean = true,\n speed: number = 1,\n): number {\n const [elapsedTime, setElapsedTime] = useState(0)\n\n useAnimationFrame((deltaTime) => {\n setElapsedTime(prev => prev + deltaTime * speed)\n }, enabled)\n\n return elapsedTime\n}\n","import { Text } from 'ink'\nimport React, { useEffect, useMemo, useState } from 'react'\nimport type { BaseEffectProps, Colour, EasingName } from '../types/index.js'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { applyOpacity, colorize } from '../utils/colors.js'\nimport { getEasingFunction } from '../utils/easing.js'\n\ninterface FadeProps extends BaseEffectProps {\n /**\n * Text colour\n * @default '#ffffff'\n * @example 'yellow'\n */\n color?: Colour\n\n /**\n * Starting opacity (0-1)\n * @default 0\n */\n from?: number\n\n /**\n * Ending opacity (0-1)\n * @default 1\n */\n to?: number\n\n /**\n * Duration in milliseconds\n * @default 1000\n */\n duration?: number\n\n /**\n * Easing function\n * @default 'ease-out'\n */\n easing?: EasingName\n\n /**\n * Loop animation continuously\n * @default false\n */\n loop?: boolean\n}\n\nconst DEFAULT_FROM = 0\nconst DEFAULT_TO = 1\nconst DEFAULT_DURATION_MS = 1000\nconst DEFAULT_EASING: EasingName = 'ease-out'\nconst DEFAULT_LOOP = false\nconst DEFAULT_SPEED = 1\n\nfunction calculateOpacity(\n elapsedTime: number,\n duration: number,\n from: number,\n to: number,\n easing: EasingName,\n): number {\n const progress = Math.min(elapsedTime / duration, 1)\n const easedProgress = getEasingFunction(easing)(progress)\n return from + (to - from) * easedProgress\n}\n\n/**\n * Fade effect component that smoothly transitions text opacity\n *\n * Animates text from one opacity to another using configurable easing functions.\n * Can loop continuously or run once.\n *\n * @example\n * ```tsx\n * <Fade color=\"yellow\" from={0} to={1} duration={500} easing=\"ease-in\">\n * Success!\n * </Fade>\n * ```\n */\nexport function Fade({\n children,\n color,\n from = DEFAULT_FROM,\n to = DEFAULT_TO,\n duration = DEFAULT_DURATION_MS,\n easing = DEFAULT_EASING,\n loop = DEFAULT_LOOP,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: FadeProps) {\n const [hasCompleted, setHasCompleted] = useState(false)\n const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed)\n\n useEffect(() => {\n if (!enabled)\n return\n\n const isComplete = elapsedTime >= duration\n\n if (isComplete && !loop && !hasCompleted) {\n setHasCompleted(true)\n onComplete?.()\n }\n }, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete])\n\n const fadedText = useMemo(() => {\n const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration)\n const opacity = calculateOpacity(effectiveTime, duration, from, to, easing)\n\n const baseColor = color ?? '#ffffff'\n const fadedColor = applyOpacity(baseColor, opacity)\n\n return colorize(children, fadedColor)\n }, [children, elapsedTime, duration, from, to, easing, color, loop])\n\n return <Text>{fadedText}</Text>\n}\n","import { Text } from 'ink'\nimport React, { useMemo } from 'react'\nimport type { BaseEffectProps, Colour } from '../types/index.js'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { mapChars } from '../utils/text.js'\n\ntype WaveType = 'brightness' | 'vertical'\n\ninterface WaveProps extends BaseEffectProps {\n /**\n * Gradient colours for the wave [dark, bright]\n * @default ['#888888', '#ffffff']\n * @example ['#ec4899', '#8b5cf6']\n */\n colors?: [Colour, Colour]\n\n /**\n * Wave height (0-1)\n * @default 0.5\n */\n amplitude?: number\n\n /**\n * Number of wave cycles across text\n * @default 2\n */\n frequency?: number\n\n /**\n * Wave effect type\n * @default 'brightness'\n */\n type?: WaveType\n}\n\nconst DEFAULT_COLORS: [Colour, Colour] = ['#888888', '#ffffff']\nconst DEFAULT_AMPLITUDE = 0.5\nconst DEFAULT_FREQUENCY = 2\nconst DEFAULT_TYPE: WaveType = 'brightness'\nconst DEFAULT_SPEED = 1\nconst FULL_CIRCLE_RADIANS = Math.PI * 2\nconst WAVE_PERIOD_MS = 2000\nconst MIN_AMPLITUDE = 0\nconst MAX_AMPLITUDE = 1\nconst SINE_WAVE_OFFSET = 1\nconst SINE_WAVE_NORMALIZE = 0.5\nconst VERTICAL_SHIFT_THRESHOLD = 0.5\n\nfunction calculateWaveValue(\n characterIndex: number,\n time: number,\n frequency: number,\n amplitude: number,\n): number {\n const clampedAmplitude = clamp(amplitude, MIN_AMPLITUDE, MAX_AMPLITUDE)\n const safeFrequency = frequency || DEFAULT_FREQUENCY\n const waveLength = FULL_CIRCLE_RADIANS / safeFrequency\n const spatialComponent = (characterIndex / waveLength) * FULL_CIRCLE_RADIANS\n const temporalComponent = time * FULL_CIRCLE_RADIANS\n const phase = spatialComponent - temporalComponent\n const sineWave = Math.sin(phase)\n const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE\n const amplitudeComplement = MAX_AMPLITUDE - clampedAmplitude\n return normalizedSine * clampedAmplitude + amplitudeComplement\n}\n\n/**\n * Wave effect component that creates a wave motion through text\n *\n * Animates text with a sine wave pattern that can affect brightness or vertical position.\n * The wave continuously flows through the text.\n *\n * @example\n * ```tsx\n * <Wave colors={['#ec4899', '#8b5cf6']} amplitude={0.7} frequency={3}>\n * ~~ wavy text ~~\n * </Wave>\n * ```\n */\nexport function Wave({\n children,\n colors = DEFAULT_COLORS,\n amplitude = DEFAULT_AMPLITUDE,\n frequency = DEFAULT_FREQUENCY,\n type = DEFAULT_TYPE,\n speed = DEFAULT_SPEED,\n enabled = true,\n}: WaveProps) {\n const elapsedTime = useElapsedTime(enabled, speed)\n const normalizedTime = elapsedTime / WAVE_PERIOD_MS\n\n const waveText = useMemo(() => {\n if (type === 'brightness') {\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const [darkColor, brightColor] = colors\n const characterColor = interpolateColor(darkColor, brightColor, waveValue)\n return colorize(character, characterColor)\n })\n }\n\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const shouldShift = waveValue > VERTICAL_SHIFT_THRESHOLD\n return shouldShift ? ` ${character}` : character\n })\n }, [children, normalizedTime, colors, amplitude, frequency, type])\n\n return <Text>{waveText}</Text>\n}\n"]}
1
+ {"version":3,"sources":["../src/hooks/useAnimationFrame.ts","../src/hooks/useElapsedTime.ts","../src/utils/colors.ts","../src/utils/easing.ts","../src/components/Fade.tsx","../src/components/Flash.tsx","../src/utils/text.ts","../src/components/Shimmer.tsx","../src/hooks/useCursorBlink.ts","../src/hooks/useTypewriterProgress.ts","../src/components/Typewriter.tsx","../src/components/Wave.tsx"],"names":["useRef","useEffect","useState","chalk","useMemo","jsx","Text","DEFAULT_DURATION_MS","DEFAULT_SPEED","MIN_INTENSITY","MAX_INTENSITY","FIRST_CHARACTER_INDEX","DEFAULT_COLORS","FULL_CIRCLE_RADIANS","SINE_WAVE_OFFSET","SINE_WAVE_NORMALIZE"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useEffect, useRef } from 'react'\n\nconst TARGET_FPS = 60\nconst FRAME_INTERVAL_MS = 1000 / TARGET_FPS\n\n/**\n * Runs a callback on every animation frame using setInterval\n *\n * @param callback - Function called each frame with delta time in ms\n * @param enabled - Whether animation is active\n */\nexport function useAnimationFrame(\n callback: (deltaTime: number) => void,\n enabled: boolean = true,\n): void {\n const intervalRef = useRef<Timer>()\n const previousTimeRef = useRef<number>()\n const callbackRef = useRef(callback)\n\n useEffect(() => {\n callbackRef.current = callback\n }, [callback])\n\n useEffect(() => {\n if (!enabled) {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n previousTimeRef.current = undefined\n return\n }\n\n intervalRef.current = setInterval(() => {\n const currentTime = Date.now()\n if (previousTimeRef.current !== undefined) {\n const deltaTime = currentTime - previousTimeRef.current\n callbackRef.current(deltaTime)\n }\n previousTimeRef.current = currentTime\n }, FRAME_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current) {\n clearInterval(intervalRef.current)\n }\n }\n }, [enabled])\n}\n","import { useState } from 'react'\nimport { useAnimationFrame } from './useAnimationFrame.js'\n\n/**\n * Hook that tracks elapsed time since mount or last reset\n *\n * @param enabled - Whether to track time\n * @param speed - Speed multiplier (default: 1)\n * @returns Elapsed time in milliseconds\n */\nexport function useElapsedTime(\n enabled: boolean = true,\n speed: number = 1,\n): number {\n const [elapsedTime, setElapsedTime] = useState(0)\n\n useAnimationFrame((deltaTime) => {\n setElapsedTime(prev => prev + deltaTime * speed)\n }, enabled)\n\n return elapsedTime\n}\n","import type { Colour } from '../types/index.js'\nimport chalk from 'chalk'\n\nconst HEX_BASE = 16\nconst DECIMAL_BASE = 10\nconst HEX_BYTE_LENGTH = 2\nconst HEX_PAD_CHARACTER = '0'\nconst MIN_RGB_COMPONENTS = 3\nconst INTERPOLATION_MIDPOINT = 0.5\n\nconst HEX_COLOR_REGEX = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i\nconst RGB_NUMBERS_REGEX = /\\d+/g\n\nfunction rgbToHex(red: number, green: number, blue: number): string {\n const redHex = red.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const greenHex = green.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n const blueHex = blue.toString(HEX_BASE).padStart(HEX_BYTE_LENGTH, HEX_PAD_CHARACTER)\n return `#${redHex}${greenHex}${blueHex}`\n}\n\n/**\n * Converts a hex color string to RGB values\n *\n * @param hex - Hex color string (with or without #)\n * @returns Tuple of [red, green, blue] values (0-255) or null if invalid\n */\nexport function hexToRgb(hex: string): [number, number, number] | null {\n const result = HEX_COLOR_REGEX.exec(hex)\n if (!result)\n return null\n\n const [, redHex, greenHex, blueHex] = result\n return [\n Number.parseInt(redHex, HEX_BASE),\n Number.parseInt(greenHex, HEX_BASE),\n Number.parseInt(blueHex, HEX_BASE),\n ]\n}\n\n/**\n * Applies color to text using chalk\n *\n * @param text - Text to colorize\n * @param color - Color as hex (#ff0000), rgb(255,0,0), or named color (red, blue, etc)\n * @returns Colorized text with ANSI escape codes\n */\nexport function colorize(text: string, color: Colour): string {\n try {\n if (color.startsWith('#'))\n return chalk.hex(color)(text)\n\n if (color.startsWith('rgb')) {\n const match = color.match(RGB_NUMBERS_REGEX)\n if (!match || match.length < MIN_RGB_COMPONENTS)\n return text\n\n const [redStr, greenStr, blueStr] = match\n const red = Number.parseInt(redStr, DECIMAL_BASE)\n const green = Number.parseInt(greenStr, DECIMAL_BASE)\n const blue = Number.parseInt(blueStr, DECIMAL_BASE)\n\n if (Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue))\n return text\n\n return chalk.rgb(red, green, blue)(text)\n }\n\n const chalkColor = chalk[color as keyof typeof chalk]\n if (typeof chalkColor === 'function')\n return chalkColor(text)\n\n return text\n }\n catch {\n return text\n }\n}\n\n/**\n * Applies opacity to a hex color by darkening it\n * Note: Terminal approximation - true opacity not possible in most terminals\n *\n * @param color - Hex color string\n * @param opacity - Opacity value from 0 (transparent/black) to 1 (fully opaque)\n * @returns Adjusted hex color or original color if not hex format\n */\nexport function applyOpacity(color: Colour, opacity: number): Colour {\n if (!color.startsWith('#'))\n return color\n\n const rgb = hexToRgb(color)\n if (!rgb)\n return color\n\n const [red, green, blue] = rgb\n const adjustedRed = Math.round(red * opacity)\n const adjustedGreen = Math.round(green * opacity)\n const adjustedBlue = Math.round(blue * opacity)\n\n return rgbToHex(adjustedRed, adjustedGreen, adjustedBlue)\n}\n\n/**\n * Interpolates between two colors\n *\n * @param color1 - Starting color\n * @param color2 - Ending color\n * @param progress - Interpolation progress from 0 to 1\n * @returns Interpolated color (hex if both inputs are hex, otherwise snaps to nearest)\n */\nexport function interpolateColor(\n color1: Colour,\n color2: Colour,\n progress: number,\n): Colour {\n const isColor1Hex = color1.startsWith('#')\n const isColor2Hex = color2.startsWith('#')\n\n if (!isColor1Hex || !isColor2Hex)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const rgb1 = hexToRgb(color1)\n const rgb2 = hexToRgb(color2)\n\n if (!rgb1 || !rgb2)\n return progress < INTERPOLATION_MIDPOINT ? color1 : color2\n\n const [red1, green1, blue1] = rgb1\n const [red2, green2, blue2] = rgb2\n\n const red = Math.round(red1 + (red2 - red1) * progress)\n const green = Math.round(green1 + (green2 - green1) * progress)\n const blue = Math.round(blue1 + (blue2 - blue1) * progress)\n\n return rgbToHex(red, green, blue)\n}\n","import type { EasingFunction, EasingName } from '../types/index.js'\n\nexport const linear: EasingFunction = time => time\n\nexport const easeIn: EasingFunction = time => time * time\n\nexport const easeOut: EasingFunction = time => time * (2 - time)\n\nexport const easeInOut: EasingFunction = (time) => {\n return time < 0.5\n ? 2 * time * time\n : -1 + (4 - 2 * time) * time\n}\n\nexport const easingFunctions: Record<EasingName, EasingFunction> = {\n 'linear': linear,\n 'ease-in': easeIn,\n 'ease-out': easeOut,\n 'ease-in-out': easeInOut,\n}\n\nexport function getEasingFunction(name: EasingName = 'linear'): EasingFunction {\n return easingFunctions[name]\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\nexport function normalize(value: number, min: number, max: number): number {\n return clamp((value - min) / (max - min), 0, 1)\n}\n\nexport function lerp(start: number, end: number, progress: number): number {\n return start + (end - start) * progress\n}\n","import type { BaseEffectProps, Colour, EasingName } from '../types/index.js'\nimport { Text } from 'ink'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { applyOpacity, colorize } from '../utils/colors.js'\nimport { getEasingFunction } from '../utils/easing.js'\n\ninterface FadeProps extends BaseEffectProps {\n /**\n * Text colour\n * @default '#ffffff'\n * @example 'yellow'\n */\n color?: Colour\n\n /**\n * Starting opacity (0-1)\n * @default 0\n */\n from?: number\n\n /**\n * Ending opacity (0-1)\n * @default 1\n */\n to?: number\n\n /**\n * Duration in milliseconds\n * @default 1000\n */\n duration?: number\n\n /**\n * Easing function\n * @default 'ease-out'\n */\n easing?: EasingName\n\n /**\n * Loop animation continuously\n * @default false\n */\n loop?: boolean\n}\n\nconst DEFAULT_FROM = 0\nconst DEFAULT_TO = 1\nconst DEFAULT_DURATION_MS = 1000\nconst DEFAULT_EASING: EasingName = 'ease-out'\nconst DEFAULT_LOOP = false\nconst DEFAULT_SPEED = 1\n\nfunction calculateOpacity(\n elapsedTime: number,\n duration: number,\n from: number,\n to: number,\n easing: EasingName,\n): number {\n const progress = Math.min(elapsedTime / duration, 1)\n const easedProgress = getEasingFunction(easing)(progress)\n return from + (to - from) * easedProgress\n}\n\n/**\n * Fade effect component that smoothly transitions text opacity\n *\n * Animates text from one opacity to another using configurable easing functions.\n * Can loop continuously or run once.\n *\n * @example\n * ```tsx\n * <Fade color=\"yellow\" from={0} to={1} duration={500} easing=\"ease-in\">\n * Success!\n * </Fade>\n * ```\n */\nexport function Fade({\n children,\n color,\n from = DEFAULT_FROM,\n to = DEFAULT_TO,\n duration = DEFAULT_DURATION_MS,\n easing = DEFAULT_EASING,\n loop = DEFAULT_LOOP,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: FadeProps) {\n const [hasCompleted, setHasCompleted] = useState(false)\n const elapsedTime = useElapsedTime(enabled && !hasCompleted, speed)\n\n useEffect(() => {\n if (!enabled)\n return\n\n const isComplete = elapsedTime >= duration\n\n if (isComplete && !loop && !hasCompleted) {\n // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect -- Intentional: prevents calling onComplete multiple times by setting completion flag once\n setHasCompleted(true)\n onComplete?.()\n }\n }, [elapsedTime, duration, loop, hasCompleted, enabled, onComplete])\n\n const fadedText = useMemo(() => {\n const effectiveTime = loop ? elapsedTime % duration : Math.min(elapsedTime, duration)\n const opacity = calculateOpacity(effectiveTime, duration, from, to, easing)\n\n const baseColor = color ?? '#ffffff'\n const fadedColor = applyOpacity(baseColor, opacity)\n\n return colorize(children, fadedColor)\n }, [children, elapsedTime, duration, from, to, easing, color, loop])\n\n return <Text>{fadedText}</Text>\n}\n","import type { BaseEffectProps, Colour } from '../types/index.js'\nimport { Text } from 'ink'\nimport { useMemo } from 'react'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { applyOpacity, colorize } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\n\ninterface FlashProps extends BaseEffectProps {\n /**\n * Text colour\n * @default '#ffffff'\n * @example 'cyan'\n */\n color?: Colour\n\n /**\n * Minimum brightness (0-1)\n * @default 0.3\n */\n minIntensity?: number\n\n /**\n * Maximum brightness (0-1)\n * @default 1\n */\n maxIntensity?: number\n\n /**\n * Flash cycle duration in milliseconds\n * @default 1000\n */\n duration?: number\n}\n\nconst DEFAULT_COLOR = '#ffffff'\nconst DEFAULT_MIN_INTENSITY = 0.3\nconst DEFAULT_MAX_INTENSITY = 1\nconst DEFAULT_DURATION_MS = 1000\nconst DEFAULT_SPEED = 1\nconst FULL_CIRCLE_RADIANS = Math.PI * 2\nconst SINE_WAVE_OFFSET = 1\nconst SINE_WAVE_NORMALIZE = 0.5\nconst MIN_INTENSITY = 0\nconst MAX_INTENSITY = 1\n\nfunction calculateFlashIntensity(\n time: number,\n duration: number,\n minIntensity: number,\n maxIntensity: number,\n): number {\n const clampedMin = clamp(minIntensity, MIN_INTENSITY, MAX_INTENSITY)\n const clampedMax = clamp(maxIntensity, MIN_INTENSITY, MAX_INTENSITY)\n const normalizedTime = time / duration\n const phase = normalizedTime * FULL_CIRCLE_RADIANS\n const sineWave = Math.sin(phase)\n const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE\n const intensityRange = clampedMax - clampedMin\n return clampedMin + normalizedSine * intensityRange\n}\n\n/**\n * Flash effect component that creates a pulsing neon-like glow\n *\n * Animates text with a continuous brightness pulse that oscillates\n * between minimum and maximum intensity levels, creating a flashing\n * or glowing neon effect.\n *\n * @example\n * ```tsx\n * <Flash color=\"cyan\" minIntensity={0.3} maxIntensity={1} duration={800}>\n * ⚡ NEON GLOW ⚡\n * </Flash>\n * ```\n */\nexport function Flash({\n children,\n color = DEFAULT_COLOR,\n minIntensity = DEFAULT_MIN_INTENSITY,\n maxIntensity = DEFAULT_MAX_INTENSITY,\n duration = DEFAULT_DURATION_MS,\n speed = DEFAULT_SPEED,\n enabled = true,\n}: FlashProps) {\n const elapsedTime = useElapsedTime(enabled, speed)\n const cycleTime = elapsedTime % duration\n\n const flashText = useMemo(() => {\n const intensity = calculateFlashIntensity(cycleTime, duration, minIntensity, maxIntensity)\n const adjustedColor = applyOpacity(color, intensity)\n return colorize(children, adjustedColor)\n }, [children, cycleTime, duration, minIntensity, maxIntensity, color])\n\n return <Text>{flashText}</Text>\n}\n","export function splitChars(text: string): string[] {\n return [...text]\n}\n\nexport function getTextLength(text: string): number {\n return [...text].length\n}\n\nexport function mapChars(\n text: string,\n fn: (char: string, index: number) => string,\n): string {\n return splitChars(text).map(fn).join('')\n}\n","import type { BaseEffectProps, Colour } from '../types/index.js'\nimport { Text } from 'ink'\nimport { useMemo, useState } from 'react'\nimport { useAnimationFrame } from '../hooks/useAnimationFrame.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { getTextLength, mapChars } from '../utils/text.js'\n\ntype ShimmerDirection = 'left' | 'right'\n\ninterface ShimmerProps extends BaseEffectProps {\n /**\n * Gradient colours for the shimmer effect [start, peak, end]\n * @default ['#666666', '#ffffff', '#666666']\n * @example ['#60a5fa', '#3b82f6', '#60a5fa']\n */\n colors?: [Colour, Colour, Colour]\n\n /**\n * Width of the shimmer band in characters\n * @default 4\n */\n width?: number\n\n /**\n * Brightness multiplier (0-1)\n * @default 1\n */\n intensity?: number\n\n /**\n * Direction of shimmer movement\n * @default 'right'\n */\n direction?: ShimmerDirection\n}\n\nconst DEFAULT_COLORS: [Colour, Colour, Colour] = ['#666666', '#ffffff', '#666666']\nconst DEFAULT_WIDTH = 4\nconst DEFAULT_INTENSITY = 1\nconst DEFAULT_DIRECTION: ShimmerDirection = 'right'\nconst DEFAULT_SPEED = 1\nconst ANIMATION_CYCLE_MS = 2000\nconst COLOUR_TRANSITION_MIDPOINT = 0.5\nconst COLOUR_PROGRESS_MULTIPLIER = 2\nconst MIN_INTENSITY = 0\nconst MAX_INTENSITY = 1\n\n/**\n * Shimmer effect component that creates a moving highlight across text\n *\n * Creates a shimmering animation by interpolating through a gradient of colours\n * that sweeps across the text from left to right or right to left.\n *\n * @example\n * ```tsx\n * <Shimmer colors={['#60a5fa', '#3b82f6', '#60a5fa']} intensity={0.8}>\n * Loading...\n * </Shimmer>\n * ```\n */\nexport function Shimmer({\n children,\n colors = DEFAULT_COLORS,\n width = DEFAULT_WIDTH,\n intensity = DEFAULT_INTENSITY,\n direction = DEFAULT_DIRECTION,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: ShimmerProps) {\n const [offset, setOffset] = useState(0)\n const textLength = getTextLength(children)\n const totalWidth = textLength + width\n\n useAnimationFrame((deltaTime) => {\n setOffset((previousOffset) => {\n const movement = (deltaTime / ANIMATION_CYCLE_MS) * speed * totalWidth\n const newOffset = direction === 'right'\n ? previousOffset + movement\n : previousOffset - movement\n\n const hasCompletedCycle = Math.abs(newOffset) >= totalWidth\n if (hasCompletedCycle) {\n onComplete?.()\n return 0\n }\n\n return newOffset\n })\n }, enabled)\n\n const shimmerText = useMemo(() => {\n return mapChars(children, (char, index) => {\n const normalizedOffset = direction === 'right' ? offset : totalWidth - offset\n const distance = Math.abs(index - normalizedOffset)\n\n const isWithinShimmerWidth = distance < width\n const colorProgress = isWithinShimmerWidth\n ? distance / width\n : 1\n\n const [startColor, peakColor, endColor] = colors\n const adjustedIntensity = clamp(intensity, MIN_INTENSITY, MAX_INTENSITY)\n\n const isFirstHalf = colorProgress < COLOUR_TRANSITION_MIDPOINT\n const currentColor = isFirstHalf\n ? interpolateColor(\n startColor,\n peakColor,\n colorProgress * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n : interpolateColor(\n peakColor,\n endColor,\n (colorProgress - COLOUR_TRANSITION_MIDPOINT) * COLOUR_PROGRESS_MULTIPLIER * adjustedIntensity,\n )\n\n return colorize(char, currentColor)\n })\n }, [children, offset, colors, width, intensity, direction, totalWidth])\n\n return <Text>{shimmerText}</Text>\n}\n","import { useEffect, useRef, useState } from 'react'\n\nconst CURSOR_BLINK_INTERVAL_MS = 500\n\ninterface UseCursorBlinkOptions {\n enabled: boolean\n isComplete: boolean\n}\n\n/**\n * Hook that manages cursor blinking state\n *\n * @param options - Configuration for cursor blink\n * @param options.enabled - Whether cursor blinking is enabled\n * @param options.isComplete - Whether typing is complete\n * @returns Whether cursor should be visible\n */\nexport function useCursorBlink({\n enabled,\n isComplete,\n}: UseCursorBlinkOptions): boolean {\n const [showCursor, setShowCursor] = useState(true)\n const intervalRef = useRef<Timer>()\n\n useEffect(() => {\n if (!enabled || isComplete) {\n // eslint-disable-next-line react-hooks-extra/no-direct-set-state-in-use-effect -- Intentional: synchronizing cursor visibility with prop changes\n setShowCursor(false)\n return\n }\n\n intervalRef.current = setInterval(() => {\n setShowCursor(previous => !previous)\n }, CURSOR_BLINK_INTERVAL_MS)\n\n return () => {\n if (intervalRef.current)\n clearInterval(intervalRef.current)\n }\n }, [enabled, isComplete])\n\n return showCursor\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { clamp } from '../utils/easing.js'\n\nconst BASE_CHARACTER_DELAY_MS = 80\nconst MIN_VARIANCE = 0\nconst MAX_VARIANCE = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction calculateCharacterDelay(\n speed: number,\n variance: number,\n): number {\n const clampedVariance = clamp(variance, MIN_VARIANCE, MAX_VARIANCE)\n const randomVariance = (Math.random() - 0.5) * clampedVariance\n return (BASE_CHARACTER_DELAY_MS / speed) * (1 + randomVariance)\n}\n\ninterface UseTypewriterProgressOptions {\n totalCharacters: number\n speed: number\n variance: number\n initialDelay: number\n enabled: boolean\n onComplete?: () => void\n}\n\n/**\n * Hook that manages typewriter character reveal timing\n *\n * @param options - Configuration for typewriter progress\n * @param options.totalCharacters - Total number of characters to reveal\n * @param options.speed - Animation speed multiplier\n * @param options.variance - Typing variance for human-like timing (0-1)\n * @param options.initialDelay - Delay before typing starts in milliseconds\n * @param options.enabled - Whether the animation is enabled\n * @param options.onComplete - Callback when typing completes\n * @returns Number of characters to display\n */\nexport function useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay,\n enabled,\n onComplete,\n}: UseTypewriterProgressOptions): number {\n const [visibleCharacters, setVisibleCharacters] = useState(0)\n const timeoutRef = useRef<Timer>()\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n\n useEffect(() => {\n if (!enabled)\n return\n\n if (hasCompletedTyping) {\n onComplete?.()\n return\n }\n\n const characterDelay = calculateCharacterDelay(speed, variance)\n const isFirstCharacter = visibleCharacters === FIRST_CHARACTER_INDEX\n const totalDelay = isFirstCharacter ? initialDelay + characterDelay : characterDelay\n\n timeoutRef.current = setTimeout(() => {\n setVisibleCharacters(previous => previous + 1)\n }, totalDelay)\n\n return () => {\n if (timeoutRef.current)\n clearTimeout(timeoutRef.current)\n }\n }, [visibleCharacters, enabled, speed, variance, initialDelay, hasCompletedTyping, onComplete])\n\n return visibleCharacters\n}\n","import type { BaseEffectProps, Colour } from '../types/index.js'\nimport { Text } from 'ink'\nimport { useMemo } from 'react'\nimport { useCursorBlink } from '../hooks/useCursorBlink.js'\nimport { useTypewriterProgress } from '../hooks/useTypewriterProgress.js'\nimport { colorize } from '../utils/colors.js'\nimport { getTextLength, splitChars } from '../utils/text.js'\n\ninterface TypewriterProps extends BaseEffectProps {\n /**\n * Text colour\n * @default undefined (inherits)\n * @example 'green'\n */\n color?: Colour\n\n /**\n * Cursor character or false to disable\n * @default '▋'\n * @example '█'\n */\n cursor?: string | boolean\n\n /**\n * Cursor colour (defaults to text colour)\n * @default undefined\n * @example 'cyan'\n */\n cursorColor?: Colour\n\n /**\n * Typing speed randomness (0-1) for more human-like typing\n * @default 0.3\n */\n variance?: number\n\n /**\n * Initial delay before typing starts (ms)\n * @default 0\n */\n delay?: number\n}\n\nconst DEFAULT_CURSOR = '▋'\nconst DEFAULT_VARIANCE = 0.3\nconst DEFAULT_DELAY = 0\nconst DEFAULT_SPEED = 1\nconst FIRST_CHARACTER_INDEX = 0\n\nfunction getCursorCharacter(cursor: string | boolean): string {\n return typeof cursor === 'string' ? cursor : DEFAULT_CURSOR\n}\n\nfunction formatCursor(\n cursorCharacter: string,\n cursorColor: Colour | undefined,\n textColor: Colour | undefined,\n): string {\n if (cursorColor)\n return colorize(cursorCharacter, cursorColor)\n\n if (textColor)\n return colorize(cursorCharacter, textColor)\n\n return cursorCharacter\n}\n\n/**\n * Typewriter effect component that reveals text character by character\n *\n * Simulates typing text with configurable speed, variance for realistic timing,\n * and an optional cursor. The cursor blinks while typing is in progress.\n *\n * @example\n * ```tsx\n * <Typewriter color=\"green\" cursor=\"█\" variance={0.5} speed={2}>\n * npm install ink-motion\n * </Typewriter>\n * ```\n */\nexport function Typewriter({\n children,\n color,\n cursor = DEFAULT_CURSOR,\n cursorColor,\n variance = DEFAULT_VARIANCE,\n delay = DEFAULT_DELAY,\n speed = DEFAULT_SPEED,\n enabled = true,\n onComplete,\n}: TypewriterProps) {\n const characters = splitChars(children)\n const totalCharacters = getTextLength(children)\n\n const visibleCharacters = useTypewriterProgress({\n totalCharacters,\n speed,\n variance,\n initialDelay: delay,\n enabled,\n onComplete,\n })\n\n const hasCompletedTyping = visibleCharacters >= totalCharacters\n const isCursorEnabled = cursor !== false\n\n const showCursor = useCursorBlink({\n enabled: enabled && isCursorEnabled,\n isComplete: hasCompletedTyping,\n })\n\n const displayText = useMemo(() => {\n const visibleText = characters.slice(FIRST_CHARACTER_INDEX, visibleCharacters).join('')\n const coloredText = color ? colorize(visibleText, color) : visibleText\n\n const shouldShowCursor = isCursorEnabled && showCursor && !hasCompletedTyping\n if (!shouldShowCursor)\n return coloredText\n\n const cursorCharacter = getCursorCharacter(cursor)\n const cursorText = formatCursor(cursorCharacter, cursorColor, color)\n\n return coloredText + cursorText\n }, [characters, visibleCharacters, color, cursor, cursorColor, showCursor, hasCompletedTyping, isCursorEnabled])\n\n return <Text>{displayText}</Text>\n}\n","import type { BaseEffectProps, Colour } from '../types/index.js'\nimport { Text } from 'ink'\nimport { useMemo } from 'react'\nimport { useElapsedTime } from '../hooks/useElapsedTime.js'\nimport { colorize, interpolateColor } from '../utils/colors.js'\nimport { clamp } from '../utils/easing.js'\nimport { mapChars } from '../utils/text.js'\n\ntype WaveType = 'brightness' | 'vertical'\n\ninterface WaveProps extends BaseEffectProps {\n /**\n * Gradient colours for the wave [dark, bright]\n * @default ['#888888', '#ffffff']\n * @example ['#ec4899', '#8b5cf6']\n */\n colors?: [Colour, Colour]\n\n /**\n * Wave height (0-1)\n * @default 0.5\n */\n amplitude?: number\n\n /**\n * Number of wave cycles across text\n * @default 2\n */\n frequency?: number\n\n /**\n * Wave effect type\n * @default 'brightness'\n */\n type?: WaveType\n}\n\nconst DEFAULT_COLORS: [Colour, Colour] = ['#888888', '#ffffff']\nconst DEFAULT_AMPLITUDE = 0.5\nconst DEFAULT_FREQUENCY = 2\nconst DEFAULT_TYPE: WaveType = 'brightness'\nconst DEFAULT_SPEED = 1\nconst FULL_CIRCLE_RADIANS = Math.PI * 2\nconst WAVE_PERIOD_MS = 2000\nconst MIN_AMPLITUDE = 0\nconst MAX_AMPLITUDE = 1\nconst SINE_WAVE_OFFSET = 1\nconst SINE_WAVE_NORMALIZE = 0.5\nconst VERTICAL_SHIFT_THRESHOLD = 0.5\n\nfunction calculateWaveValue(\n characterIndex: number,\n time: number,\n frequency: number,\n amplitude: number,\n): number {\n const clampedAmplitude = clamp(amplitude, MIN_AMPLITUDE, MAX_AMPLITUDE)\n const safeFrequency = frequency || DEFAULT_FREQUENCY\n const waveLength = FULL_CIRCLE_RADIANS / safeFrequency\n const spatialComponent = (characterIndex / waveLength) * FULL_CIRCLE_RADIANS\n const temporalComponent = time * FULL_CIRCLE_RADIANS\n const phase = spatialComponent - temporalComponent\n const sineWave = Math.sin(phase)\n const normalizedSine = (sineWave + SINE_WAVE_OFFSET) * SINE_WAVE_NORMALIZE\n const amplitudeComplement = MAX_AMPLITUDE - clampedAmplitude\n return normalizedSine * clampedAmplitude + amplitudeComplement\n}\n\n/**\n * Wave effect component that creates a wave motion through text\n *\n * Animates text with a sine wave pattern that can affect brightness or vertical position.\n * The wave continuously flows through the text.\n *\n * @example\n * ```tsx\n * <Wave colors={['#ec4899', '#8b5cf6']} amplitude={0.7} frequency={3}>\n * ~~ wavy text ~~\n * </Wave>\n * ```\n */\nexport function Wave({\n children,\n colors = DEFAULT_COLORS,\n amplitude = DEFAULT_AMPLITUDE,\n frequency = DEFAULT_FREQUENCY,\n type = DEFAULT_TYPE,\n speed = DEFAULT_SPEED,\n enabled = true,\n}: WaveProps) {\n const elapsedTime = useElapsedTime(enabled, speed)\n const normalizedTime = elapsedTime / WAVE_PERIOD_MS\n\n const waveText = useMemo(() => {\n if (type === 'brightness') {\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const [darkColor, brightColor] = colors\n const characterColor = interpolateColor(darkColor, brightColor, waveValue)\n return colorize(character, characterColor)\n })\n }\n\n return mapChars(children, (character, index) => {\n const waveValue = calculateWaveValue(index, normalizedTime, frequency, amplitude)\n const shouldShift = waveValue > VERTICAL_SHIFT_THRESHOLD\n return shouldShift ? ` ${character}` : character\n })\n }, [children, normalizedTime, colors, amplitude, frequency, type])\n\n return <Text>{waveText}</Text>\n}\n"]}