UNPKG

@kya-os/cli

Version:

CLI for KYA-OS MCP-I setup and management

394 lines 14.9 kB
/** * Decrypt Effect * Movie-style text decryption effect with typing animation */ import { BaseEffect, } from "../types.js"; import { ANSI } from "../utils.js"; /** * Decrypt effect implementation */ export class DecryptEffect extends BaseEffect { constructor(options = {}) { super(); this.name = "Decrypt"; this.description = "Movie-style text decryption effect with typing animation"; this.characterStates = new Map(); this.animationFrameCount = 0; this.totalFrames = 0; this.typingQueue = []; this.activeTypingChars = new Set(); this.decryptingChars = new Set(); this._isEffectComplete = false; // Character sets for encryption this.encryptedSymbols = []; this.typingSymbols = ["▉", "▓", "▒", "░"]; this.options = { duration: 5000, typingSpeed: 1, ciphertextColors: ["008000", "00cb00", "00ff00"], finalColor: "eda000", useGradient: true, gradientDirection: "vertical", typingProbability: 0.75, fastReveal: false, revealCompletionRatio: 0.3, ...options, }; this.initializeEncryptedSymbols(); } /** * Initialize the pool of encrypted symbols */ initializeEncryptedSymbols() { // Keyboard characters for (let i = 33; i < 127; i++) { this.encryptedSymbols.push(String.fromCharCode(i)); } // Box drawing characters for (let i = 9472; i < 9599; i++) { this.encryptedSymbols.push(String.fromCharCode(i)); } // Block characters for (let i = 9608; i < 9632; i++) { this.encryptedSymbols.push(String.fromCharCode(i)); } // Misc symbols for (let i = 174; i < 452; i++) { this.encryptedSymbols.push(String.fromCharCode(i)); } } /** * Initialize the effect */ onInitialize() { this.totalFrames = Math.floor((this.options.duration / 1000) * this.config.frameRate); // Initialize character states this.characterStates.clear(); this.typingQueue = [...this.characters]; this.activeTypingChars.clear(); this.decryptingChars.clear(); // Shuffle typing queue for random order this.shuffleArray(this.typingQueue); // Calculate final colors for gradient effect const dimensions = this.getCanvasDimensions(); const gradientColors = this.calculateGradientColors(dimensions); for (const char of this.characters) { const key = `${char.originalPosition.x},${char.originalPosition.y}`; let finalColor = this.options.finalColor; if (this.options.useGradient) { const gradientKey = this.options.gradientDirection === "vertical" ? char.originalPosition.y : char.originalPosition.x; finalColor = gradientColors[gradientKey] || this.options.finalColor; } this.characterStates.set(key, { phase: "hidden", typingFrame: 0, decryptStartTime: -1, currentSymbol: char.originalSymbol, currentColor: this.getRandomCiphertextColor(), finalColor, decryptSpeed: Math.floor(Math.random() * 3) + 2, decryptChangesRemaining: Math.floor(Math.random() * 15) + 80, discoveryFrame: 0, }); } this.animationFrameCount = 0; this._isEffectComplete = false; } /** * Calculate gradient colors based on dimensions */ calculateGradientColors(dimensions) { const gradientMap = {}; const size = this.options.gradientDirection === "vertical" ? dimensions.height : dimensions.width; // Simple linear gradient from ciphertext color to final color const startColor = this.options.ciphertextColors[this.options.ciphertextColors.length - 1]; for (let i = 0; i < size; i++) { const factor = i / (size - 1); gradientMap[i] = this.interpolateColor(startColor, this.options.finalColor, factor); } return gradientMap; } /** * 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 && state.phase !== "hidden") { const visual = this.createCharacterVisual(char, state); char.visual = visual; const { x, y } = char.currentPosition; if (y >= 0 && y < dimensions.height && x >= 0 && x < dimensions.width) { // Apply ANSI color codes 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() { // Phase 1: Typing animation if (this.typingQueue.length > 0 || this.activeTypingChars.size > 0) { this.updateTypingPhase(); } else if (this.decryptingChars.size === 0) { // Transition to decryption phase for (const char of this.characters) { const key = `${char.originalPosition.x},${char.originalPosition.y}`; const state = this.characterStates.get(key); if (state) { state.phase = "fast_decrypt"; state.decryptStartTime = this.animationFrameCount; this.decryptingChars.add(key); } } } // Phase 2: Decryption animation if (this.decryptingChars.size > 0) { this.updateDecryptionPhase(); } // Check if effect is complete this._isEffectComplete = this.checkCompletion(); } /** * Update typing phase animation */ updateTypingPhase() { // Start typing new characters if (this.typingQueue.length > 0 && Math.random() < this.options.typingProbability) { for (let i = 0; i < this.options.typingSpeed && this.typingQueue.length > 0; i++) { const char = this.typingQueue.shift(); const key = `${char.originalPosition.x},${char.originalPosition.y}`; const state = this.characterStates.get(key); if (state) { state.phase = "typing"; state.typingFrame = 0; this.activeTypingChars.add(key); } } } // Update active typing characters for (const key of this.activeTypingChars) { const state = this.characterStates.get(key); if (state) { state.typingFrame++; // Complete typing animation after showing all typing symbols if (state.typingFrame > this.typingSymbols.length * 2 + 4) { state.phase = "fast_decrypt"; this.activeTypingChars.delete(key); } } } } /** * Update decryption phase animation */ updateDecryptionPhase() { const completedKeys = []; for (const key of this.decryptingChars) { const state = this.characterStates.get(key); if (!state) continue; const framesSinceStart = this.animationFrameCount - state.decryptStartTime; if (state.phase === "fast_decrypt") { // Fast decrypt phase - rapid symbol changes if (framesSinceStart % 3 === 0) { state.currentSymbol = this.getRandomEncryptedSymbol(); state.decryptChangesRemaining--; } // Transition to slow decrypt after initial burst if (state.decryptChangesRemaining <= 15) { state.phase = "slow_decrypt"; state.decryptSpeed = Math.floor(Math.random() * 50) + 25; } } else if (state.phase === "slow_decrypt") { // Slow decrypt phase - less frequent changes if (framesSinceStart % state.decryptSpeed === 0) { state.currentSymbol = this.getRandomEncryptedSymbol(); state.decryptChangesRemaining--; // 30% chance of extra long duration if (Math.random() < 0.3) { state.decryptSpeed = Math.floor(Math.random() * 75) + 50; } else { state.decryptSpeed = Math.floor(Math.random() * 5) + 5; } } // Transition to discovered if (state.decryptChangesRemaining <= 0) { state.phase = "discovered"; state.discoveryFrame = 0; const [x, y] = key.split(",").map(Number); const char = this.characters.find(c => c.originalPosition.x === x && c.originalPosition.y === y); if (char) { state.currentSymbol = char.originalSymbol; } } } else if (state.phase === "discovered") { // Discovery fade-in effect state.discoveryFrame++; if (state.discoveryFrame > 10) { completedKeys.push(key); } } } // Remove completed characters from decrypting set for (const key of completedKeys) { this.decryptingChars.delete(key); } } /** * Create visual representation of a character */ createCharacterVisual(char, state) { let symbol = char.originalSymbol; let color = state.currentColor; switch (state.phase) { case "typing": // Typing animation with block characters const typingIndex = Math.floor(state.typingFrame / 2) % this.typingSymbols.length; symbol = state.typingFrame < this.typingSymbols.length * 2 ? this.typingSymbols[typingIndex] : state.currentSymbol; color = this.getRandomCiphertextColor(); break; case "fast_decrypt": case "slow_decrypt": symbol = state.currentSymbol; color = state.currentColor; break; case "discovered": // Fade from white to final color const fadeFactor = Math.min(state.discoveryFrame / 10, 1); color = this.interpolateColor("ffffff", state.finalColor, fadeFactor); symbol = char.originalSymbol; break; } return { symbol, colors: { fg: color, bg: null, }, }; } /** * Get a random ciphertext color */ getRandomCiphertextColor() { return this.options.ciphertextColors[Math.floor(Math.random() * this.options.ciphertextColors.length)]; } /** * Get a random encrypted symbol */ getRandomEncryptedSymbol() { return this.encryptedSymbols[Math.floor(Math.random() * this.encryptedSymbols.length)]; } /** * Check if the effect is complete */ checkCompletion() { if (this.animationFrameCount >= this.totalFrames) { return true; } for (const state of this.characterStates.values()) { if (state.phase !== "discovered" || state.discoveryFrame < 10) { return false; } } return true; } /** * Shuffle array in place */ shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } } /** * 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.typingQueue = []; this.activeTypingChars.clear(); this.decryptingChars.clear(); this.animationFrameCount = 0; this._isEffectComplete = false; this.onInitialize(); } } //# sourceMappingURL=decrypt.js.map