UNPKG

@kya-os/cli

Version:

CLI for KYA-OS MCP-I setup and management

278 lines 10.9 kB
/** * Waves Effect * Creates wave patterns that travel across the text */ import { BaseEffect, } from "../types.js"; import { ANSI } from "../utils.js"; import { Gradient, GradientDirection, EASING } from "../gradient.js"; /** * Waves effect implementation */ export class WavesEffect extends BaseEffect { constructor(options = {}) { super(); this.name = "Waves"; this.description = "Creates wave patterns that travel across the text"; this.characterStates = new Map(); this.animationFrameCount = 0; this.totalFrames = 0; this.wavePosition = 0; this._isEffectComplete = false; this.options = { duration: 4000, waveColors: ["0080ff", "00a0ff", "00c0ff", "00e0ff", "00ffff", "80ffff", "ffffff"], finalColor: "ffffff", waveSpeed: 0.15, waveFrequency: 2, waveAmplitude: 3, direction: "horizontal", useGradient: true, ...options, }; } /** * Initialize the effect */ onInitialize() { this.totalFrames = Math.floor((this.options.duration / 1000) * this.config.frameRate); // Create gradient if enabled if (this.options.useGradient) { this.gradient = new Gradient({ stops: this.options.waveColors, steps: 10, direction: this.options.direction === "vertical" ? GradientDirection.HORIZONTAL : GradientDirection.VERTICAL, easingFn: EASING.inOutSine, }); } // Initialize character states this.characterStates.clear(); const dimensions = this.getCanvasDimensions(); for (const char of this.characters) { const key = `${char.originalPosition.x},${char.originalPosition.y}`; // Calculate wave offset based on position and direction let waveOffset = 0; switch (this.options.direction) { case "horizontal": waveOffset = char.originalPosition.x / dimensions.width; break; case "vertical": waveOffset = char.originalPosition.y / dimensions.height; break; case "diagonal": waveOffset = (char.originalPosition.x + char.originalPosition.y) / (dimensions.width + dimensions.height); break; } this.characterStates.set(key, { phase: "waiting", waveOffset, waveIntensity: 0, originalY: char.originalPosition.y, currentWaveY: char.originalPosition.y, colorIndex: 0, settleProgress: 0, }); } this.wavePosition = -0.5; // Start wave off-screen this.animationFrameCount = 0; this._isEffectComplete = false; } /** * Render the next frame */ async render() { if (!this.isInitialized) { throw new Error("Effect not initialized"); } // Update animation state this.updateAnimationState(); // Create the frame const dimensions = this.getCanvasDimensions(); const frame = Array(dimensions.height) .fill("") .map(() => Array(dimensions.width).fill(" ")); // Render characters for (const char of this.characters) { const key = `${char.originalPosition.x},${char.originalPosition.y}`; const state = this.characterStates.get(key); if (state) { const visual = this.createCharacterVisual(char, state); char.visual = visual; // Use wave-displaced position for horizontal/diagonal waves let x = char.originalPosition.x; let y = char.originalPosition.y; if (this.options.direction !== "vertical" && state.phase === "waving") { y = Math.round(state.currentWaveY); } else if (this.options.direction === "vertical" && state.phase === "waving") { // For vertical waves, displace horizontally x = Math.round(char.originalPosition.x + Math.sin(state.currentWaveY * Math.PI * this.options.waveFrequency) * this.options.waveAmplitude); } if (y >= 0 && y < dimensions.height && x >= 0 && x < dimensions.width) { const colorCode = this.getColorCode(visual.colors.fg); const resetCode = ANSI.reset; frame[y][x] = colorCode + visual.symbol + resetCode; } } } this.animationFrameCount++; // Convert frame to strings return frame.map((row) => row.join("")); } /** * Update animation state for all characters */ updateAnimationState() { const progress = this.animationFrameCount / this.totalFrames; // Update wave position this.wavePosition += this.options.waveSpeed; // Update character states for (const [key, state] of this.characterStates) { const [_x, _y] = key.split(",").map(Number); // Calculate wave influence const waveDistance = Math.abs(this.wavePosition - state.waveOffset); const waveInfluence = Math.max(0, 1 - waveDistance * 2); if (waveInfluence > 0 && state.phase !== "complete") { state.phase = "waving"; state.waveIntensity = waveInfluence; // Calculate wave displacement const wavePhase = (state.waveOffset + this.wavePosition) * Math.PI * 2 * this.options.waveFrequency; const waveHeight = Math.sin(wavePhase) * this.options.waveAmplitude * waveInfluence; if (this.options.direction !== "vertical") { state.currentWaveY = state.originalY + waveHeight; } else { // For vertical waves, we'll use this for horizontal displacement state.currentWaveY = wavePhase; } // Update color based on wave position if (this.gradient) { const spectrum = this.gradient.getSpectrum(); state.colorIndex = Math.floor(waveInfluence * (spectrum.length - 1)); } else { state.colorIndex = Math.floor(waveInfluence * (this.options.waveColors.length - 1)); } } else if (state.phase === "waving" && waveInfluence === 0) { // Start settling state.phase = "settling"; } // Handle settling phase if (state.phase === "settling") { state.settleProgress += 0.05; state.currentWaveY = state.originalY + (state.currentWaveY - state.originalY) * (1 - state.settleProgress); if (state.settleProgress >= 1) { state.phase = "complete"; state.currentWaveY = state.originalY; } } } // Check if effect is complete this._isEffectComplete = progress >= 1 || Array.from(this.characterStates.values()).every(s => s.phase === "complete"); } /** * Create visual representation of a character */ createCharacterVisual(char, state) { let color = this.options.finalColor; switch (state.phase) { case "waiting": // Use dimmed color while waiting color = "404040"; break; case "waving": // Use wave color based on intensity if (this.gradient) { const spectrum = this.gradient.getSpectrum(); color = spectrum[state.colorIndex] || spectrum[spectrum.length - 1]; } else { color = this.options.waveColors[state.colorIndex] || this.options.waveColors[this.options.waveColors.length - 1]; } break; case "settling": // Interpolate from wave color to final color const waveColor = this.gradient ? this.gradient.getSpectrum()[0] : this.options.waveColors[0]; color = this.interpolateColor(waveColor, this.options.finalColor, state.settleProgress); break; case "complete": color = this.options.finalColor; break; } return { symbol: char.originalSymbol, colors: { fg: color, bg: null, }, }; } /** * Get ANSI color code for a color */ getColorCode(color) { if (!color || this.config.noColor) { return ""; } // For RGB colors, convert to ANSI 24-bit color if (typeof color === "string") { const r = parseInt(color.substring(0, 2), 16); const g = parseInt(color.substring(2, 4), 16); const b = parseInt(color.substring(4, 6), 16); return `\x1b[38;2;${r};${g};${b}m`; } // For XTerm colors return `\x1b[38;5;${color}m`; } /** * Interpolate between two colors */ interpolateColor(color1, color2, factor) { const r1 = parseInt(color1.substring(0, 2), 16); const g1 = parseInt(color1.substring(2, 4), 16); const b1 = parseInt(color1.substring(4, 6), 16); const r2 = parseInt(color2.substring(0, 2), 16); const g2 = parseInt(color2.substring(2, 4), 16); const b2 = parseInt(color2.substring(4, 6), 16); const r = Math.round(r1 + (r2 - r1) * factor); const g = Math.round(g1 + (g2 - g1) * factor); const b = Math.round(b1 + (b2 - b1) * factor); return [r, g, b] .map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, "0")) .join(""); } /** * Render fallback for when effects are disabled */ renderFallback() { return this.text.split("\n"); } /** * Check if effect is complete */ isComplete() { return this._isEffectComplete; } /** * Reset the effect */ onReset() { this.characterStates.clear(); this.wavePosition = -0.5; this.animationFrameCount = 0; this._isEffectComplete = false; this.onInitialize(); } } //# sourceMappingURL=waves.js.map