UNPKG

@kya-os/cli

Version:

CLI for MCP-I setup and management

221 lines 8.03 kB
/** * Beams Effect * Creates beams which travel over the canvas illuminating the characters */ import { BaseEffect } from "../types.js"; import { ANSI, easeInOutSine } from "../utils.js"; /** * Beams effect implementation */ export class BeamsEffect extends BaseEffect { constructor(options = {}) { super(); this.name = "Beams"; this.description = "Creates beams which travel over the canvas illuminating the characters"; this.beams = []; this.animationFrameCount = 0; this.totalFrames = 0; this.nextBeamId = 0; this.lastBeamSpawn = 0; this._isEffectComplete = false; this.options = { duration: 5000, beamCount: 8, beamWidth: 5, beamSpeedRange: [1.0, 3.0], beamColors: ["ffffff", "00D1FF", "8A008A"], illuminationBrightness: 2.0, baseColor: "202020", ...options, }; } /** * Initialize the effect */ onInitialize() { this.totalFrames = Math.floor((this.options.duration / 1000) * this.config.frameRate); this.beams = []; this.animationFrameCount = 0; this.nextBeamId = 0; this.lastBeamSpawn = 0; this._isEffectComplete = false; // Set base color for all characters for (const char of this.characters) { char.visual.colors.fg = this.options.baseColor; } } /** * Render the next frame */ async render() { if (!this.isInitialized) { throw new Error("Effect not initialized"); } // Update beams this.updateBeams(); // Render text with beam effects line by line const lines = this.text.split("\n"); const renderedLines = []; for (let y = 0; y < lines.length; y++) { let line = ""; const chars = [...lines[y]]; for (let x = 0; x < chars.length; x++) { const char = chars[x]; let color = this.options.baseColor; // Check if character is illuminated by any beam for (const beam of this.beams) { if (!beam.active) continue; const distance = beam.direction === "horizontal" ? Math.abs(y - beam.position.y) : Math.abs(x - beam.position.x); if (distance <= beam.width) { const intensity = 1 - distance / beam.width; const easedIntensity = easeInOutSine(intensity); const beamColor = this.getBeamColor(beam); color = this.applyIllumination(color, beamColor, easedIntensity); } } const colorCode = this.getColorCode(color); const resetCode = ANSI.reset; line += colorCode + char + resetCode; } renderedLines.push(line); } this.animationFrameCount++; return renderedLines; } /** * Update beam positions and states */ updateBeams() { const dimensions = this.getCanvasDimensions(); const spawnInterval = Math.floor(this.totalFrames / (this.options.beamCount * 3)); // Spawn new beams more frequently if (this.beams.filter(b => b.active).length < this.options.beamCount && this.animationFrameCount - this.lastBeamSpawn > spawnInterval && this.animationFrameCount < this.totalFrames * 0.8) { this.spawnBeam(); this.lastBeamSpawn = this.animationFrameCount; } // Update existing beams for (const beam of this.beams) { if (!beam.active) continue; // Update position based on direction if (beam.direction === "horizontal") { beam.position.x += beam.speed; // Deactivate if off screen if (beam.position.x > dimensions.width + beam.width) { beam.active = false; } } else { beam.position.y += beam.speed; // Deactivate if off screen if (beam.position.y > dimensions.height + beam.width) { beam.active = false; } } } // Check if effect is complete const activeBeams = this.beams.filter((b) => b.active).length; this._isEffectComplete = activeBeams === 0 && this.animationFrameCount >= this.totalFrames * 0.8; } /** * Spawn a new beam */ spawnBeam() { const dimensions = this.getCanvasDimensions(); const isHorizontal = Math.random() > 0.5; const beam = { id: this.nextBeamId++, position: { x: isHorizontal ? -this.options.beamWidth : Math.random() * dimensions.width, y: isHorizontal ? Math.random() * dimensions.height : -this.options.beamWidth, }, direction: isHorizontal ? "horizontal" : "vertical", speed: this.options.beamSpeedRange[0] + Math.random() * (this.options.beamSpeedRange[1] - this.options.beamSpeedRange[0]), width: this.options.beamWidth, progress: 0, active: true, }; this.beams.push(beam); } /** * Get beam color based on its properties */ getBeamColor(beam) { // Cycle through colors based on beam ID const colorIndex = beam.id % this.options.beamColors.length; return this.options.beamColors[colorIndex]; } /** * Apply illumination to a base color */ applyIllumination(baseColor, beamColor, intensity) { // Parse colors const baseR = parseInt(baseColor.substring(0, 2), 16); const baseG = parseInt(baseColor.substring(2, 4), 16); const baseB = parseInt(baseColor.substring(4, 6), 16); const beamR = parseInt(beamColor.substring(0, 2), 16); const beamG = parseInt(beamColor.substring(2, 4), 16); const beamB = parseInt(beamColor.substring(4, 6), 16); // Apply illumination with brightness multiplier const brightness = 1 + (this.options.illuminationBrightness - 1) * intensity; const r = Math.min(255, Math.round(baseR + (beamR - baseR) * intensity * brightness)); const g = Math.min(255, Math.round(baseG + (beamG - baseG) * intensity * brightness)); const b = Math.min(255, Math.round(baseB + (beamB - baseB) * intensity * brightness)); return [r, g, b] .map((c) => Math.max(0, Math.min(255, c)).toString(16).padStart(2, "0")) .join(""); } /** * 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`; } /** * 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.beams = []; this.animationFrameCount = 0; this.nextBeamId = 0; this.lastBeamSpawn = 0; this._isEffectComplete = false; this.onInitialize(); } } //# sourceMappingURL=beams.js.map