diff --git a/assets/js/games/engine/game-engine.js b/assets/js/games/engine/game-engine.js index f4e2c8e..650c61c 100644 --- a/assets/js/games/engine/game-engine.js +++ b/assets/js/games/engine/game-engine.js @@ -6,6 +6,7 @@ class GameEngine { this.adapter = null; this.state = null; this.input = null; + this.sound = null; this.scenes = null; this.isRunning = false; @@ -53,7 +54,13 @@ class GameEngine { this.adapter = new TerminalAdapter(this.terminal); this.state = new StateManager(this.definition.id); this.input = new InputManager(this.adapter); - this.scenes = new SceneManager(this.adapter, this.state, this.input); + + // Initialize sound manager if SoundManager is available + if (window.SoundManager) { + this.sound = new SoundManager(this.adapter); + } + + this.scenes = new SceneManager(this.adapter, this.state, this.input, this.sound); // Initialize state this.state.init(this.definition.initialState || {}); @@ -173,6 +180,11 @@ class GameEngine { this.isRunning = false; + // Cleanup sound manager + if (this.sound) { + this.sound.stopAll(); + } + // Cleanup input manager if (this.input) { this.input.destroy(); diff --git a/assets/js/games/engine/input-manager.js b/assets/js/games/engine/input-manager.js index 6d4cbef..547fa23 100644 --- a/assets/js/games/engine/input-manager.js +++ b/assets/js/games/engine/input-manager.js @@ -18,7 +18,8 @@ class InputManager { // Check if terminal input has text - if so, let user submit commands like "quit" const terminalInput = document.getElementById("input"); - const hasInputText = terminalInput && terminalInput.value.trim().length > 0; + const hasInputText = + terminalInput && terminalInput.value.trim().length > 0; if (e.key === "ArrowUp") { e.preventDefault(); @@ -85,6 +86,7 @@ class InputManager { if (prompt) { this.adapter.print(""); + this.adapter.printInfo("------------------"); this.adapter.printInfo(prompt); } this.adapter.print(""); diff --git a/assets/js/games/engine/scene-manager.js b/assets/js/games/engine/scene-manager.js index 412f38a..e55f9e5 100644 --- a/assets/js/games/engine/scene-manager.js +++ b/assets/js/games/engine/scene-manager.js @@ -1,12 +1,14 @@ // Scene Manager - Handles scene definitions, rendering, and transitions class SceneManager { - constructor(adapter, stateManager, inputManager) { + constructor(adapter, stateManager, inputManager, soundManager = null) { this.adapter = adapter; this.state = stateManager; this.input = inputManager; + this.sound = soundManager; this.scenes = {}; this.currentScene = null; this.sceneHistory = []; + this.activeSounds = new Map(); // Track sounds started in current scene } // Register scenes from game definition @@ -34,8 +36,16 @@ class SceneManager { this.sceneHistory.push(this.currentScene.id); } + // Stop scene-specific sounds from previous scene + await this._cleanupSceneSounds(); + this.currentScene = scene; + // Preload sounds for this scene + if (this.sound && scene.sounds) { + await this._preloadSceneSounds(scene.sounds); + } + // Execute onEnter actions if (scene.onEnter) { await this._executeActions(scene.onEnter); @@ -145,7 +155,11 @@ class SceneManager { } if (block.type === "typewriter") { - await this._typewriter(block.text, block.speed || 50); + await this._typewriter(block.text, block.speed || 50, { + bold: block.bold, + italic: block.italic, + className: block.className, + }); continue; } @@ -154,12 +168,20 @@ class SceneManager { continue; } + if (block.type === "sound") { + await this._handleSound(block); + continue; + } + // Text with optional className (supports html: true for HTML content) if (block.text !== undefined) { if (block.html) { this._printHTML(block.text, block.className || ""); } else { - this._printText(block.text, block.className || ""); + this._printText(block.text, block.className || "", { + bold: block.bold, + italic: block.italic, + }); } continue; } @@ -167,15 +189,22 @@ class SceneManager { } // Print text with variable interpolation - _printText(text, className = "") { + _printText(text, className = "", options = {}) { // Support ${path} interpolation const interpolated = text.replace(/\$\{([^}]+)\}/g, (match, path) => { const value = this.state.get(path); return value !== undefined ? String(value) : match; }); - if (className) { - this.adapter.print(interpolated, className); + // Build style classes based on options + let styleClasses = className; + if (options.bold) + styleClasses += (styleClasses ? " " : "") + "typewriter-bold"; + if (options.italic) + styleClasses += (styleClasses ? " " : "") + "typewriter-italic"; + + if (styleClasses) { + this.adapter.print(interpolated, styleClasses); } else { this.adapter.print(interpolated); } @@ -190,7 +219,9 @@ class SceneManager { }); if (className) { - this.adapter.printHTML(`${interpolated}`); + this.adapter.printHTML( + `${interpolated}`, + ); } else { this.adapter.printHTML(interpolated); } @@ -350,10 +381,16 @@ class SceneManager { } // Typewriter effect - async _typewriter(text, speed) { + async _typewriter(text, speed, options = {}) { const interpolated = this._interpolateText(text); let output = ""; + // Build style classes based on options + let styleClasses = "typewriter-line"; + if (options.bold) styleClasses += " typewriter-bold"; + if (options.italic) styleClasses += " typewriter-italic"; + if (options.className) styleClasses += " " + options.className; + for (const char of interpolated) { output += char; // Create a single updating line for typewriter @@ -364,7 +401,7 @@ class SceneManager { typewriterSpan.textContent = output; } else { this.adapter.printHTML( - `${output}`, + `${output}`, ); } @@ -451,4 +488,127 @@ class SceneManager { resetHistory() { this.sceneHistory = []; } + + // Preload sounds for a scene + async _preloadSceneSounds(sounds) { + if (!this.sound) return; + + const soundList = Array.isArray(sounds) ? sounds : [sounds]; + let hasShownLoading = false; + + for (const soundDef of soundList) { + const soundId = soundDef.id; + const url = soundDef.url || soundDef.src; + + if (!soundId || !url) { + console.warn("Invalid sound definition:", soundDef); + continue; + } + + // Skip if already loaded + if (this.sound.isLoaded(soundId)) { + continue; + } + + // Show loading indicator if not shown yet + if (!hasShownLoading) { + this.adapter.printHTML( + 'Loading audio...', + ); + hasShownLoading = true; + } + + try { + await this.sound.preload(soundId, url); + } catch (error) { + console.error(`Failed to preload sound ${soundId}:`, error); + // Continue loading other sounds even if one fails + } + } + + // Remove loading indicator + if (hasShownLoading) { + const indicator = + this.adapter.terminal.output.querySelector(".sound-loading"); + if (indicator) { + indicator.remove(); + } + } + } + + // Handle sound playback in content blocks + async _handleSound(block) { + if (!this.sound) { + console.warn("Sound manager not available"); + return; + } + + const action = block.action || "play"; // play, stop, stopAll + const soundId = block.id || block.sound; + + try { + if (action === "play") { + const options = { + loop: block.loop || false, + volume: block.volume !== undefined ? block.volume : 1.0, + fade: block.fade || false, + fadeDuration: block.fadeDuration || 1000, + }; + + const controller = await this.sound.play(soundId, options); + + // Store reference for cleanup unless it's a one-shot sound + if (block.loop || block.persist) { + this.activeSounds.set(soundId, controller); + } + + // Auto-stop after duration if specified + if (block.duration) { + setTimeout(() => { + if (block.fadeOut !== false) { + controller.fadeOut(block.fadeDuration || 1000); + } else { + controller.stop(); + } + }, block.duration); + } + } else if (action === "stop") { + const controller = this.activeSounds.get(soundId); + if (controller) { + if (block.fadeOut !== false) { + await controller.fadeOut(block.fadeDuration || 1000); + } else { + controller.stop(); + } + this.activeSounds.delete(soundId); + } + } else if (action === "stopAll") { + await this._cleanupSceneSounds(); + } + } catch (error) { + console.error(`Sound error (${action} ${soundId}):`, error); + // Don't show error to user, just log it + } + } + + // Clean up sounds when leaving a scene + async _cleanupSceneSounds() { + if (!this.sound) return; + + const fadePromises = []; + + for (const [, controller] of this.activeSounds) { + if (controller.fadeOut) { + fadePromises.push( + controller.fadeOut(500).catch((e) => console.error("Fade error:", e)), + ); + } else { + controller.stop(); + } + } + + // Wait for all fades to complete + await Promise.all(fadePromises); + this.activeSounds.clear(); + } } diff --git a/assets/js/games/engine/sound-manager.js b/assets/js/games/engine/sound-manager.js new file mode 100644 index 0000000..ce4cfa8 --- /dev/null +++ b/assets/js/games/engine/sound-manager.js @@ -0,0 +1,247 @@ +// Sound Manager - Handles audio loading, caching, and playback for games +class SoundManager { + constructor(adapter) { + this.adapter = adapter; + this.sounds = new Map(); // soundId -> { audio, loaded, loading, error } + this.currentlyPlaying = new Set(); // Track currently playing sounds + this.globalVolume = 1.0; + } + + // Preload a sound file + async preload(soundId, url) { + // If already loaded or loading, return existing promise + if (this.sounds.has(soundId)) { + const sound = this.sounds.get(soundId); + if (sound.loaded) { + return sound.audio; + } + if (sound.loading) { + return sound.loadingPromise; + } + if (sound.error) { + throw new Error(`Sound ${soundId} failed to load: ${sound.error}`); + } + } + + // Create new audio element + const audio = new Audio(); + const soundEntry = { + audio, + loaded: false, + loading: true, + error: null, + url, + }; + + // Create promise for loading + const loadingPromise = new Promise((resolve, reject) => { + const onLoad = () => { + soundEntry.loaded = true; + soundEntry.loading = false; + audio.removeEventListener("canplaythrough", onLoad); + audio.removeEventListener("error", onError); + resolve(audio); + }; + + const onError = (e) => { + soundEntry.loading = false; + soundEntry.error = e.message || "Failed to load audio"; + audio.removeEventListener("canplaythrough", onLoad); + audio.removeEventListener("error", onError); + reject(new Error(`Failed to load sound ${soundId}: ${soundEntry.error}`)); + }; + + audio.addEventListener("canplaythrough", onLoad, { once: true }); + audio.addEventListener("error", onError, { once: true }); + audio.preload = "auto"; + audio.src = url; + audio.load(); + }); + + soundEntry.loadingPromise = loadingPromise; + this.sounds.set(soundId, soundEntry); + + return loadingPromise; + } + + // Play a sound (will load if not already loaded) + async play(soundId, options = {}) { + const { + loop = false, + volume = 1.0, + onEnd = null, + fade = false, + fadeDuration = 1000, + } = options; + + let soundEntry = this.sounds.get(soundId); + + if (!soundEntry) { + throw new Error(`Sound ${soundId} not preloaded. Use preload() first.`); + } + + // Wait for loading if still loading + if (soundEntry.loading) { + await soundEntry.loadingPromise; + } + + if (soundEntry.error) { + throw new Error(`Sound ${soundId} failed to load: ${soundEntry.error}`); + } + + const audio = soundEntry.audio; + + // Clone the audio element for concurrent playback + const playInstance = audio.cloneNode(); + playInstance.loop = loop; + playInstance.volume = fade ? 0 : volume * this.globalVolume; + + // Track this instance + const trackingId = `${soundId}_${Date.now()}`; + this.currentlyPlaying.add(trackingId); + + // Handle end event + const cleanup = () => { + this.currentlyPlaying.delete(trackingId); + playInstance.removeEventListener("ended", cleanup); + if (onEnd) onEnd(); + }; + + playInstance.addEventListener("ended", cleanup); + + // Play the sound + try { + await playInstance.play(); + + // Fade in if requested + if (fade) { + this._fadeIn(playInstance, volume * this.globalVolume, fadeDuration); + } + + return { + instance: playInstance, + stop: () => { + playInstance.pause(); + playInstance.currentTime = 0; + cleanup(); + }, + fadeOut: (duration = fadeDuration) => { + return this._fadeOut(playInstance, duration).then(() => { + playInstance.pause(); + cleanup(); + }); + }, + }; + } catch (error) { + cleanup(); + throw new Error(`Failed to play sound ${soundId}: ${error.message}`); + } + } + + // Fade in audio + _fadeIn(audio, targetVolume, duration) { + const steps = 20; + const stepDuration = duration / steps; + const volumeStep = targetVolume / steps; + let currentStep = 0; + + const interval = setInterval(() => { + currentStep++; + audio.volume = Math.min(volumeStep * currentStep, targetVolume); + + if (currentStep >= steps) { + clearInterval(interval); + audio.volume = targetVolume; + } + }, stepDuration); + } + + // Fade out audio + _fadeOut(audio, duration) { + return new Promise((resolve) => { + const steps = 20; + const stepDuration = duration / steps; + const startVolume = audio.volume; + const volumeStep = startVolume / steps; + let currentStep = 0; + + const interval = setInterval(() => { + currentStep++; + audio.volume = Math.max(startVolume - volumeStep * currentStep, 0); + + if (currentStep >= steps) { + clearInterval(interval); + audio.volume = 0; + resolve(); + } + }, stepDuration); + }); + } + + // Stop all currently playing sounds + stopAll() { + for (const soundId of Array.from(this.currentlyPlaying)) { + const [id] = soundId.split("_"); + const soundEntry = this.sounds.get(id); + if (soundEntry && soundEntry.audio) { + soundEntry.audio.pause(); + soundEntry.audio.currentTime = 0; + } + } + this.currentlyPlaying.clear(); + } + + // Set global volume (0.0 to 1.0) + setGlobalVolume(volume) { + this.globalVolume = Math.max(0, Math.min(1, volume)); + } + + // Check if a sound is loaded + isLoaded(soundId) { + const sound = this.sounds.get(soundId); + return sound && sound.loaded; + } + + // Check if a sound is currently loading + isLoading(soundId) { + const sound = this.sounds.get(soundId); + return sound && sound.loading; + } + + // Get loading progress for all sounds + getLoadingStatus() { + const status = { + total: this.sounds.size, + loaded: 0, + loading: 0, + failed: 0, + }; + + for (const [, sound] of this.sounds) { + if (sound.loaded) status.loaded++; + else if (sound.loading) status.loading++; + else if (sound.error) status.failed++; + } + + return status; + } + + // Clear all sounds (useful for cleanup) + clear() { + this.stopAll(); + this.sounds.clear(); + } + + // Remove a specific sound from cache + unload(soundId) { + const sound = this.sounds.get(soundId); + if (sound && sound.audio) { + sound.audio.pause(); + sound.audio.src = ""; + } + this.sounds.delete(soundId); + } +} + +// Make available globally +window.SoundManager = SoundManager; diff --git a/assets/js/games/games/ascii-art.js b/assets/js/games/games/ascii-art.js index 7b7adad..90599e4 100644 --- a/assets/js/games/games/ascii-art.js +++ b/assets/js/games/games/ascii-art.js @@ -46,24 +46,15 @@ const DARK_TOWER_HEADER = ` `; const LIGHTHOUSE_HEADER = ` -\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[48;5;0m \x1b[38;5;17;48;5;0m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;178;48;5;0m▄\x1b[38;5;178;48;5;233m▄\x1b[38;5;220;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;208m▄\x1b[38;5;208;48;5;130m▄\x1b[38;5;0;48;5;160m▄\x1b[38;5;166;48;5;52m▄\x1b[38;5;130;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;178;48;5;0m▄▄\x1b[38;5;3;48;5;0m▄\x1b[38;5;236;48;5;232m▄\x1b[38;5;238;48;5;230m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;0;48;5;23m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;184m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄\x1b[48;5;0m \x1b[m -\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;94;48;5;0m▄\x1b[38;5;100;48;5;220m▄\x1b[38;5;178;48;5;0m▄\x1b[38;5;136;48;5;0m▄\x1b[38;5;3;48;5;0m▄\x1b[38;5;220;48;5;0m▄\x1b[38;5;136;48;5;0m▄\x1b[38;5;202;48;5;232m▄\x1b[38;5;220;48;5;58m▄\x1b[38;5;0;48;5;172m▄\x1b[38;5;178;48;5;11m▄\x1b[38;5;214;48;5;232m▄\x1b[38;5;11;48;5;94m▄\x1b[38;5;143;48;5;172m▄\x1b[38;5;11;48;5;236m▄\x1b[38;5;229;48;5;234m▄\x1b[38;5;220;48;5;220m▄\x1b[38;5;11;48;5;94m▄\x1b[38;5;220;48;5;232m▄\x1b[38;5;208;48;5;11m▄\x1b[38;5;0;48;5;178m▄\x1b[38;5;136;48;5;52m▄\x1b[38;5;11;48;5;220m▄\x1b[38;5;11;48;5;0m▄\x1b[38;5;94;48;5;18m▄\x1b[38;5;220;48;5;0m▄▄\x1b[38;5;11;48;5;0m▄\x1b[38;5;184;48;5;0m▄\x1b[38;5;58;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;3;48;5;238m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;233;48;5;232m▄\x1b[38;5;0;48;5;58m▄\x1b[38;5;232;48;5;220m▄\x1b[38;5;0;48;5;184m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;234m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[38;5;0;48;5;74m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;73;48;5;0m▄\x1b[38;5;235;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;232;48;5;4m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;25;48;5;17m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;23m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;73m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[m -\x1b[38;5;60;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;100;48;5;242m▄\x1b[38;5;94;48;5;220m▄\x1b[38;5;233;48;5;220m▄\x1b[38;5;232;48;5;214m▄\x1b[38;5;58;48;5;149m▄\x1b[38;5;58;48;5;11m▄\x1b[38;5;184;48;5;185m▄\x1b[38;5;94;48;5;101m▄\x1b[38;5;220;48;5;88m▄\x1b[38;5;94;48;5;11m▄\x1b[38;5;178;48;5;227m▄\x1b[38;5;11;48;5;228m▄\x1b[38;5;11;48;5;230m▄\x1b[38;5;101;48;5;15m▄\x1b[38;5;11;48;5;229m▄\x1b[38;5;11;48;5;15m▄\x1b[38;5;229;48;5;15m▄\x1b[38;5;11;48;5;15m▄▄\x1b[38;5;184;48;5;15m▄\x1b[38;5;11;48;5;15m▄\x1b[38;5;11;48;5;230m▄\x1b[38;5;11;48;5;228m▄\x1b[38;5;214;48;5;228m▄\x1b[38;5;178;48;5;11m▄\x1b[38;5;232;48;5;11m▄\x1b[38;5;184;48;5;11m▄\x1b[38;5;11;48;5;11m▄\x1b[38;5;184;48;5;227m▄\x1b[38;5;142;48;5;11m▄\x1b[38;5;3;48;5;220m▄\x1b[38;5;58;48;5;220m▄\x1b[38;5;94;48;5;220m▄\x1b[38;5;3;48;5;184m▄\x1b[38;5;100;48;5;221m▄\x1b[38;5;136;48;5;234m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;4;48;5;17m▄\x1b[38;5;236;48;5;17m▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;236;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;20;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;4;48;5;24m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[48;5;0m \x1b[m -\x1b[38;5;220;48;5;237m▄\x1b[38;5;11;48;5;0m▄\x1b[38;5;11;48;5;234m▄\x1b[38;5;232;48;5;100m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;235m▄\x1b[38;5;0;48;5;94m▄\x1b[38;5;0;48;5;184m▄\x1b[38;5;0;48;5;220m▄\x1b[38;5;0;48;5;214m▄\x1b[38;5;232;48;5;58m▄\x1b[38;5;240;48;5;0m▄\x1b[38;5;136;48;5;0m▄\x1b[38;5;94;48;5;233m▄\x1b[38;5;220;48;5;0m▄\x1b[38;5;52;48;5;184m▄\x1b[38;5;94;48;5;178m▄\x1b[38;5;17;48;5;178m▄\x1b[38;5;3;48;5;172m▄\x1b[38;5;220;48;5;220m▄\x1b[38;5;227;48;5;220m▄\x1b[38;5;178;48;5;136m▄\x1b[38;5;0;48;5;220m▄\x1b[38;5;232;48;5;214m▄\x1b[38;5;130;48;5;166m▄\x1b[38;5;214;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;232m▄\x1b[38;5;232;48;5;136m▄\x1b[38;5;0;48;5;11m▄\x1b[38;5;0;48;5;220m▄\x1b[38;5;0;48;5;11m▄\x1b[38;5;0;48;5;142m▄\x1b[38;5;0;48;5;178m▄\x1b[38;5;0;48;5;136m▄\x1b[38;5;232;48;5;58m▄\x1b[38;5;178;48;5;3m▄\x1b[38;5;227;48;5;94m▄\x1b[38;5;229;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;60;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;20;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;4;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;233;48;5;232m▄\x1b[38;5;17;48;5;4m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄\x1b[38;5;232;48;5;184m▄\x1b[38;5;232;48;5;0m▄▄\x1b[38;5;0;48;5;172m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;58m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[38;5;0;48;5;107m▄\x1b[38;5;239;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;1m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;94;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;0;48;5;252m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;17m▄▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;67m▄\x1b[38;5;0;48;5;232m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;17;48;5;24m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[38;5;232;48;5;0m▄\x1b[38;5;232;48;5;67m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[38;5;0;48;5;232m▄▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;24;48;5;236m▄\x1b[38;5;227;48;5;24m▄\x1b[38;5;43;48;5;122m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;235;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄▄▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;4;48;5;17m▄\x1b[38;5;240;48;5;0m▄\x1b[38;5;15;48;5;232m▄\x1b[38;5;15;48;5;0m▄\x1b[38;5;15;48;5;17m▄\x1b[38;5;15;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;230;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;15;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;15;48;5;0m▄▄▄\x1b[38;5;230;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;232m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;19m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;17;48;5;17m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄\x1b[m -\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[38;5;85;48;5;0m▄\x1b[38;5;0;48;5;234m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;18;48;5;26m▄\x1b[48;5;0m \x1b[38;5;0;48;5;233m▄\x1b[38;5;65;48;5;101m▄\x1b[38;5;192;48;5;230m▄\x1b[38;5;115;48;5;31m▄\x1b[38;5;236;48;5;0m▄\x1b[38;5;0;48;5;234m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;19m▄\x1b[38;5;18;48;5;25m▄\x1b[38;5;19;48;5;20m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;232;48;5;19m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;62;48;5;17m▄\x1b[38;5;27;48;5;62m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;17;48;5;4m▄\x1b[38;5;232;48;5;18m▄\x1b[38;5;232;48;5;19m▄\x1b[38;5;17;48;5;19m▄\x1b[38;5;18;48;5;178m▄\x1b[38;5;236;48;5;24m▄\x1b[38;5;227;48;5;15m▄\x1b[38;5;0;48;5;17m▄▄\x1b[38;5;11;48;5;230m▄\x1b[38;5;11;48;5;18m▄\x1b[38;5;11;48;5;19m▄\x1b[38;5;227;48;5;15m▄\x1b[38;5;232;48;5;4m▄\x1b[38;5;11;48;5;15m▄\x1b[38;5;11;48;5;232m▄\x1b[38;5;227;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[48;5;0m \x1b[38;5;19;48;5;17m▄\x1b[38;5;19;48;5;19m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;4;48;5;18m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;17;48;5;0m▄\x1b[38;5;17;48;5;24m▄\x1b[38;5;4;48;5;17m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;3;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;58m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;23;48;5;74m▄\x1b[38;5;0;48;5;227m▄\x1b[38;5;229;48;5;229m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;17;48;5;0m▄▄▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;172;48;5;0m▄\x1b[38;5;0;48;5;24m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[48;5;0m \x1b[38;5;179;48;5;0m▄\x1b[38;5;1;48;5;234m▄\x1b[38;5;215;48;5;4m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;235;48;5;234m▄\x1b[38;5;11;48;5;142m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;184;48;5;100m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;4m▄\x1b[38;5;227;48;5;178m▄\x1b[38;5;237;48;5;0m▄\x1b[38;5;227;48;5;184m▄\x1b[38;5;220;48;5;0m▄\x1b[38;5;11;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;242;48;5;0m▄\x1b[38;5;234;48;5;17m▄\x1b[38;5;214;48;5;227m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;19;48;5;232m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;4;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;4;48;5;232m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;0;48;5;229m▄\x1b[38;5;17;48;5;25m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;94;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;17;48;5;11m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;232;48;5;108m▄\x1b[38;5;6;48;5;229m▄\x1b[38;5;6;48;5;79m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;15;48;5;255m▄\x1b[38;5;230;48;5;255m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;232;48;5;233m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;15;48;5;108m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;15;48;5;230m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;235;48;5;178m▄\x1b[38;5;15;48;5;230m▄\x1b[38;5;230;48;5;229m▄\x1b[38;5;233;48;5;232m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;15;48;5;230m▄\x1b[38;5;24;48;5;237m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;228;48;5;15m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;230;48;5;255m▄\x1b[38;5;230;48;5;229m▄\x1b[38;5;232;48;5;94m▄\x1b[38;5;15;48;5;230m▄\x1b[38;5;230;48;5;186m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;230;48;5;60m▄\x1b[38;5;0;48;5;94m▄\x1b[38;5;230;48;5;94m▄\x1b[38;5;230;48;5;229m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;15;48;5;230m▄\x1b[38;5;230;48;5;143m▄\x1b[38;5;101;48;5;94m▄\x1b[38;5;230;48;5;61m▄\x1b[38;5;229;48;5;60m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;233;48;5;17m▄\x1b[38;5;229;48;5;238m▄\x1b[38;5;230;48;5;237m▄\x1b[38;5;15;48;5;0m▄\x1b[38;5;229;48;5;60m▄\x1b[38;5;230;48;5;67m▄\x1b[38;5;230;48;5;187m▄\x1b[38;5;230;48;5;223m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;228;48;5;228m▄\x1b[38;5;230;48;5;222m▄\x1b[38;5;230;48;5;7m▄\x1b[38;5;230;48;5;188m▄\x1b[38;5;15;48;5;229m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;18;48;5;0m▄▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;4m▄\x1b[38;5;17;48;5;24m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;31m▄\x1b[38;5;25;48;5;17m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;25;48;5;17m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;25;48;5;25m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;58m▄\x1b[38;5;25;48;5;17m▄\x1b[38;5;24;48;5;235m▄\x1b[38;5;31;48;5;232m▄\x1b[38;5;230;48;5;44m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;229;48;5;15m▄\x1b[38;5;228;48;5;15m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;229;48;5;15m▄\x1b[38;5;15;48;5;15m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;229;48;5;15m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;229;48;5;18m▄\x1b[38;5;11;48;5;19m▄\x1b[38;5;0;48;5;166m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;227;48;5;19m▄\x1b[38;5;228;48;5;0m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;233;48;5;61m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;255;48;5;15m▄\x1b[38;5;228;48;5;230m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;228;48;5;15m▄\x1b[38;5;228;48;5;230m▄\x1b[38;5;227;48;5;17m▄\x1b[38;5;255;48;5;17m▄\x1b[38;5;230;48;5;230m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;192;48;5;230m▄\x1b[38;5;255;48;5;230m▄\x1b[38;5;232;48;5;18m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;15;48;5;15m▄\x1b[38;5;229;48;5;230m▄\x1b[38;5;228;48;5;15m▄\x1b[38;5;227;48;5;229m▄\x1b[38;5;0;48;5;240m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;227;48;5;229m▄\x1b[38;5;230;48;5;15m▄\x1b[38;5;229;48;5;15m▄\x1b[38;5;228;48;5;230m▄\x1b[38;5;228;48;5;232m▄\x1b[38;5;228;48;5;4m▄\x1b[38;5;228;48;5;17m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;227;48;5;229m▄\x1b[38;5;227;48;5;230m▄\x1b[38;5;229;48;5;17m▄\x1b[38;5;227;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;19;48;5;232m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;17m▄\x1b[m -\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;38;48;5;232m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;4;48;5;19m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;17;48;5;25m▄\x1b[38;5;26;48;5;31m▄\x1b[38;5;25;48;5;31m▄\x1b[38;5;194;48;5;15m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;26m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;158;48;5;194m▄\x1b[38;5;195;48;5;230m▄\x1b[38;5;80;48;5;0m▄\x1b[38;5;122;48;5;0m▄\x1b[38;5;66;48;5;0m▄\x1b[38;5;195;48;5;255m▄\x1b[38;5;159;48;5;255m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;152;48;5;254m▄\x1b[38;5;122;48;5;188m▄\x1b[38;5;79;48;5;0m▄\x1b[38;5;158;48;5;65m▄\x1b[38;5;158;48;5;187m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;159;48;5;253m▄\x1b[38;5;158;48;5;187m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;255;48;5;151m▄\x1b[38;5;233;48;5;232m▄\x1b[38;5;17;48;5;0m▄\x1b[48;5;0m \x1b[38;5;194;48;5;254m▄\x1b[38;5;151;48;5;194m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;255;48;5;230m▄\x1b[38;5;159;48;5;187m▄\x1b[38;5;0;48;5;232m▄▄\x1b[38;5;158;48;5;194m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;122;48;5;193m▄\x1b[38;5;80;48;5;187m▄\x1b[38;5;158;48;5;0m▄\x1b[38;5;159;48;5;0m▄\x1b[38;5;194;48;5;255m▄\x1b[38;5;194;48;5;229m▄\x1b[38;5;158;48;5;151m▄\x1b[38;5;116;48;5;180m▄\x1b[38;5;195;48;5;0m▄\x1b[38;5;158;48;5;232m▄\x1b[38;5;116;48;5;186m▄\x1b[38;5;80;48;5;253m▄\x1b[38;5;116;48;5;0m▄▄\x1b[38;5;152;48;5;0m▄\x1b[38;5;152;48;5;237m▄\x1b[38;5;86;48;5;187m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;79;48;5;150m▄\x1b[38;5;80;48;5;151m▄\x1b[38;5;80;48;5;0m▄\x1b[38;5;158;48;5;232m▄\x1b[38;5;158;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[48;5;0m \x1b[m -\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;24;48;5;31m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;37;48;5;32m▄\x1b[38;5;44;48;5;25m▄\x1b[38;5;31;48;5;37m▄\x1b[38;5;23;48;5;158m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;15m▄▄\x1b[38;5;0;48;5;159m▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;66m▄\x1b[38;5;0;48;5;15m▄▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;255m▄\x1b[38;5;0;48;5;15m▄▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;15m▄▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;15m▄▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;195m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;15m▄▄▄\x1b[38;5;0;48;5;255m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;159m▄\x1b[38;5;0;48;5;15m▄▄▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;195m▄\x1b[38;5;232;48;5;15m▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;159m▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;195m▄\x1b[38;5;18;48;5;15m▄\x1b[38;5;0;48;5;15m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;184m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;0;48;5;23m▄\x1b[38;5;24;48;5;23m▄\x1b[38;5;6;48;5;23m▄\x1b[38;5;37;48;5;31m▄\x1b[38;5;31;48;5;24m▄\x1b[38;5;31;48;5;37m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;30;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;37;48;5;0m▄\x1b[38;5;25;48;5;25m▄\x1b[38;5;37;48;5;6m▄\x1b[38;5;121;48;5;230m▄\x1b[38;5;31;48;5;25m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;25;48;5;0m▄▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;19;48;5;4m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;32;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;19;48;5;19m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;4m▄\x1b[m -\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;6;48;5;0m▄\x1b[38;5;31;48;5;24m▄\x1b[38;5;24;48;5;235m▄\x1b[38;5;17;48;5;31m▄\x1b[38;5;37;48;5;17m▄\x1b[38;5;44;48;5;232m▄\x1b[38;5;44;48;5;30m▄\x1b[38;5;19;48;5;19m▄\x1b[38;5;0;48;5;19m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄▄▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;227;48;5;24m▄\x1b[38;5;67;48;5;31m▄\x1b[38;5;6;48;5;24m▄\x1b[38;5;4;48;5;31m▄\x1b[38;5;192;48;5;31m▄\x1b[38;5;193;48;5;26m▄\x1b[38;5;115;48;5;25m▄\x1b[38;5;25;48;5;23m▄\x1b[38;5;25;48;5;17m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;116;48;5;17m▄\x1b[38;5;79;48;5;0m▄\x1b[38;5;17;48;5;31m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;24;48;5;37m▄\x1b[38;5;4;48;5;31m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;24;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;37;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;17;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;17;48;5;0m▄▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;19m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;4;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄\x1b[m -\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;17;48;5;31m▄\x1b[38;5;233;48;5;31m▄\x1b[38;5;17;48;5;23m▄\x1b[38;5;23;48;5;80m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;18m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;17;48;5;0m▄\x1b[48;5;0m \x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;137;48;5;24m▄\x1b[38;5;17;48;5;235m▄\x1b[38;5;4;48;5;193m▄\x1b[38;5;157;48;5;72m▄\x1b[38;5;114;48;5;115m▄\x1b[38;5;71;48;5;31m▄\x1b[38;5;24;48;5;31m▄\x1b[38;5;24;48;5;115m▄\x1b[38;5;24;48;5;79m▄\x1b[38;5;24;48;5;24m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;17;48;5;79m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;6;48;5;24m▄\x1b[38;5;24;48;5;37m▄\x1b[38;5;17;48;5;24m▄\x1b[38;5;30;48;5;24m▄\x1b[38;5;24;48;5;24m▄\x1b[38;5;4;48;5;23m▄\x1b[38;5;17;48;5;31m▄\x1b[38;5;23;48;5;234m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;32;48;5;232m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;20m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;17;48;5;0m▄▄\x1b[38;5;0;48;5;17m▄▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;184m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[m -\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;31m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;18;48;5;0m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;25;48;5;25m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;31m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;6;48;5;232m▄\x1b[38;5;242;48;5;232m▄\x1b[38;5;17;48;5;31m▄\x1b[38;5;0;48;5;26m▄\x1b[38;5;32;48;5;151m▄\x1b[38;5;229;48;5;37m▄\x1b[38;5;229;48;5;36m▄\x1b[38;5;228;48;5;79m▄\x1b[38;5;78;48;5;6m▄\x1b[38;5;155;48;5;24m▄\x1b[38;5;229;48;5;30m▄\x1b[38;5;77;48;5;24m▄\x1b[38;5;193;48;5;24m▄\x1b[38;5;157;48;5;24m▄\x1b[38;5;122;48;5;79m▄\x1b[38;5;121;48;5;25m▄\x1b[38;5;86;48;5;24m▄\x1b[38;5;44;48;5;18m▄\x1b[38;5;44;48;5;17m▄\x1b[38;5;31;48;5;25m▄\x1b[38;5;38;48;5;17m▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;45;48;5;17m▄\x1b[38;5;32;48;5;19m▄\x1b[38;5;32;48;5;17m▄\x1b[38;5;38;48;5;17m▄\x1b[38;5;24;48;5;0m▄▄\x1b[38;5;32;48;5;19m▄\x1b[38;5;17;48;5;18m▄\x1b[38;5;4;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;27;48;5;0m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;26;48;5;25m▄\x1b[38;5;26;48;5;18m▄\x1b[38;5;27;48;5;17m▄\x1b[38;5;27;48;5;0m▄\x1b[38;5;20;48;5;0m▄\x1b[38;5;26;48;5;0m▄▄▄\x1b[38;5;27;48;5;0m▄\x1b[38;5;27;48;5;19m▄\x1b[38;5;27;48;5;232m▄\x1b[38;5;19;48;5;0m▄▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;18;48;5;0m▄\x1b[38;5;26;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[m -\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;19;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;17;48;5;0m▄▄\x1b[38;5;30;48;5;25m▄\x1b[38;5;17;48;5;23m▄\x1b[38;5;152;48;5;18m▄\x1b[38;5;24;48;5;25m▄\x1b[38;5;4;48;5;25m▄\x1b[38;5;31;48;5;17m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;17;48;5;18m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;24;48;5;24m▄\x1b[38;5;24;48;5;25m▄\x1b[38;5;67;48;5;25m▄\x1b[38;5;17;48;5;24m▄\x1b[38;5;18;48;5;17m▄\x1b[38;5;233;48;5;17m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;24;48;5;17m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;17m▄▄▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;4;48;5;17m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;232;48;5;0m▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄\x1b[48;5;0m \x1b[m -\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;0;48;5;19m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;17;48;5;19m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;235;48;5;0m▄\x1b[38;5;67;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;23;48;5;25m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;233m▄\x1b[38;5;24;48;5;24m▄\x1b[38;5;31;48;5;38m▄\x1b[38;5;31;48;5;0m▄\x1b[38;5;25;48;5;236m▄\x1b[38;5;4;48;5;193m▄\x1b[38;5;0;48;5;23m▄\x1b[38;5;229;48;5;24m▄\x1b[38;5;17;48;5;17m▄\x1b[38;5;187;48;5;24m▄\x1b[38;5;65;48;5;17m▄\x1b[38;5;194;48;5;0m▄\x1b[38;5;6;48;5;24m▄\x1b[38;5;229;48;5;73m▄\x1b[38;5;72;48;5;30m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;4m▄\x1b[38;5;232;48;5;4m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;0;48;5;116m▄\x1b[38;5;0;48;5;6m▄\x1b[38;5;0;48;5;81m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;0;48;5;123m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;26;48;5;25m▄\x1b[38;5;24;48;5;32m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;17;48;5;0m▄\x1b[38;5;0;48;5;37m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;0;48;5;38m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;25;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[38;5;17;48;5;0m▄▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;233;48;5;0m▄\x1b[38;5;234;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[m -\x1b[38;5;0;48;5;0m▄▄▄▄▄▄▄▄▄\x1b[38;5;0;48;5;23m▄\x1b[38;5;32;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄\x1b[48;5;0m \x1b[38;5;4;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;232;48;5;24m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;73m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;0;48;5;80m▄\x1b[38;5;238;48;5;0m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;6m▄\x1b[38;5;0;48;5;25m▄\x1b[38;5;0;48;5;17m▄\x1b[38;5;17;48;5;232m▄\x1b[38;5;232;48;5;232m▄\x1b[38;5;24;48;5;0m▄\x1b[38;5;79;48;5;0m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;32;48;5;0m▄\x1b[38;5;241;48;5;0m▄\x1b[38;5;66;48;5;0m▄\x1b[38;5;232;48;5;17m▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;66;48;5;0m▄\x1b[38;5;0;48;5;238m▄\x1b[38;5;0;48;5;116m▄\x1b[38;5;0;48;5;238m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;236m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄\x1b[38;5;23;48;5;232m▄\x1b[38;5;80;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;23;48;5;0m▄\x1b[38;5;81;48;5;234m▄\x1b[38;5;23;48;5;236m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄\x1b[48;5;0m \x1b[38;5;0;48;5;0m▄▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[38;5;0;48;5;18m▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;0;48;5;232m▄\x1b[38;5;0;48;5;0m▄▄▄▄▄▄\x1b[38;5;0;48;5;24m▄\x1b[38;5;232;48;5;0m▄▄\x1b[38;5;0;48;5;0m▄▄\x1b[38;5;232;48;5;0m▄\x1b[38;5;0;48;5;0m▄\x1b[48;5;0m \x1b[m - + . + /|\\ + / | \\ + / | \\ + / | \\ + / | \\ + / .--+--. \\ + /__/ | \\__\\ + [__] | [__] + | + ~~~~~~~|~~~~~~~ `; diff --git a/assets/js/games/games/boxing-day.js b/assets/js/games/games/boxing-day.js index 9bfeba1..e52334b 100644 --- a/assets/js/games/games/boxing-day.js +++ b/assets/js/games/games/boxing-day.js @@ -1,22 +1,6 @@ // Boxing Day - Day 1: December 26, 1999 // A BBS-themed mystery game -// ASCII Art Constants - -// const LIGHTHOUSE_HEADER = ` -// . -// /|\\ -// / | \\ -// / | \\ -// / | \\ -// / | \\ -// / .--+--. \\ -// /__/ | \\__\\ -// [__] | [__] -// | -// ~~~~~~~~~|~~~~~~~~~ -// `; - const GLITCH_ART = ` ▓▓▓▒▒░░ E̸̢R̷̨R̵̢O̸̧R̷̨ ░░▒▒▓▓▓ ░▒▓█ D̶̨A̷̧T̸̢Ą̵ C̷̢Ǫ̸Ŗ̵R̷̨U̸̢P̵̧T̷̨ █▓▒░ @@ -51,7 +35,7 @@ const boxingDayGame = { found_number: false, dialed_lighthouse: false, - // rm -rf deletion flags (persist across sessions) + // deletion flags (this needs to persist across game sessions somehow... TBD) archives_deleted: false, corrupted_file_deleted: false, @@ -62,19 +46,23 @@ const boxingDayGame = { intro: [ { type: "ansi", art: BOXING_DAY_TITLE, className: "game-ansi-art center" }, "", - { text: "December 26, 1999", className: "info" }, - { text: "11:47 PM", className: "info" }, + { text: "December 26, 1999 - 10:47 PM", className: "info" }, "", { type: "delay", ms: 600 }, "Five days until the millennium.", - { type: "delay", ms: 400 }, + { type: "delay", ms: 1500 }, "Five days until everything might change.", "", - { type: "delay", ms: 600 }, + { type: "delay", ms: 1000 }, "Your 56k modem hums quietly in the dark.", "The house is silent. Everyone else is asleep.", "", { type: "delay", ms: 400 }, + { + text: "This game occasionally plays sounds, mute your tab now if that offends you.", + html: true, + className: "warning", + }, { text: 'Type "quit" at any time to save and exit.', className: "info" }, ], @@ -87,39 +75,48 @@ const boxingDayGame = { connect_prompt: { content: [ - "Your terminal awaits a command.", - "", - { text: "The familiar glow illuminates your face.", className: "info" }, + { + text: "Your terminal awaits a command.", + html: true, + className: "info", + }, + { + text: "The familiar glow illuminates your face.", + html: true, + className: "info", + }, ], options: [{ text: "Connect to Dark Tower BBS", next: "modem_connect" }], }, modem_connect: { clear: true, + // Preload sounds for this scene + sounds: [{ id: "modem_connect", url: "/audio/modem-connect.mp3" }], content: [ { type: "typewriter", text: "ATDT 555-0199", speed: 80 }, - { type: "delay", ms: 800 }, "", + // Play modem dial sound + { type: "sound", id: "modem_connect", volume: 0.6 }, + { type: "delay", ms: 400 }, { text: "DIALING...", className: "info" }, - { type: "delay", ms: 600 }, - "", - { text: "RING", className: "warning" }, - { type: "delay", ms: 800 }, - { text: "RING", className: "warning" }, - { type: "delay", ms: 600 }, + { type: "delay", ms: 3000 }, "", { type: "typewriter", - text: "~~ eEe ~~ EEE ~~ eee ~~ CONNECT 56000", + text: "~~ eEe ~~ EEE ~~ eee ~~", speed: 35, + italic: true, }, - { type: "delay", ms: 400 }, + { type: "delay", ms: 3000 }, + "CONNECT 56000", "", { text: "Carrier detected.", className: "success" }, - { type: "delay", ms: 300 }, - { text: "Negotiating protocol...", className: "info" }, - { type: "delay", ms: 500 }, + { type: "delay", ms: 4500 }, + "Negotiating protocol...", + { type: "delay", ms: 4500 }, { text: "Connection established.", className: "success" }, + { type: "delay", ms: 2000 }, ], next: "dark_tower_main", delay: 1200, @@ -139,16 +136,16 @@ const boxingDayGame = { }, "", { - text: "---=[ B B S - E S T. 1 9 9 5 ]=---", + text: "---=[ D A R K T O W E R B B S - E S T. 1 9 9 5 ]=---", className: "info center", }, { - text: "[ Users Online - 3 ] - [ SysOp - NightWatchman ]", + text: "[ Users Connected - 3 ] - [ SysOp - NightWatchman ]", className: "info center", }, - { text: "[ Local Time: 11:52 PM ]", className: "info center" }, + { text: "[ Local Time: 10:52 PM ]", className: "info center" }, "", - // New message notification (only if not read) + // New message notification if not read { condition: { not: "read_new_message" }, content: [ @@ -159,15 +156,6 @@ const boxingDayGame = { "", ], }, - // Return visitor message - { - condition: "visited.dark_tower_main", - content: { - text: "Returning to main menu...", - html: true, - className: "info", - }, - }, ], onAfterRender: [{ set: "visited.dark_tower_main", value: true }], prompt: "Select:", @@ -199,7 +187,6 @@ const boxingDayGame = { // ========================================== read_messages: { - title: "Private Messages", content: [ ...TableHelper.table({ title: "Private Messages for 0BSERVER0", @@ -209,7 +196,7 @@ const boxingDayGame = { "23", "[UNKNOWN]", "0BSERVER0", - "25/12", + "24/12", { text: "NEW", className: "warning" }, ], ["22", "NIGHTWATCHER", "0BSERVER0", "12/12", "READ"], @@ -227,7 +214,6 @@ const boxingDayGame = { }, new_message: { - title: "Reading Message...", content: [ { type: "delay", ms: 300 }, "─── BEGIN MESSAGE ───", @@ -276,6 +262,11 @@ const boxingDayGame = { { type: "delay", ms: 800 }, "", ], + /** + * Update variables for read messages and found number + * + * The option the user takes here determines the path taken for this chapter + */ onEnter: [ { set: "read_new_message", value: true }, { set: "found_number", value: true }, @@ -301,35 +292,28 @@ const boxingDayGame = { }, message_archive: { - title: "Message Archive", content: [ { type: "table", - title: "Private Messages for ${username}", // Supports interpolation + title: "Private Messages for 0BSERVER0", headers: ["#", "FROM", "TO", "DATE", "STATUS"], rows: [ - // Simple array row (always shown) - ["22", "NIGHTWAT.", "0BSERVER0", "12/12", "READ"], - - // Conditional row - only shown if condition is true + // Conditionally display watcher message as read or not { condition: { not: "read_new_message" }, - cells: ["23", "[UNKNOWN]", "0BSERVER0", "25/12", "NEW"], - className: "warning", // Applied to all cells in row + cells: ["23", "[UNKNOWN]", "0BSERVER0", "24/12", "NEW"], + className: "warning", }, - - // Row with per-cell styling { - cells: [ - "21", - "0BSERVER0", - { text: "DELETED", className: "error" }, - "11/12", - "SENT", - ], + condition: "read_new_message", + cells: ["23", "[UNKNOWN]", "0BSERVER0", "24/12", "READ"], }, - // Conditional with complex logic + ["22", "NIGHTWATCHER", "0BSERVER0", "12/12", "READ"], + ["21", "0BSERVER0", "NIGHTWATCHER", "11/12", "SENT"], + ["22", "NIGHTWATCHER", "0BSERVER0", "10/12", "READ"], + + // Testing the advanced condition stuff... { condition: { and: ["has_secret", { not: "revealed_secret" }] }, cells: ["99", "???", "???", "??/??", "HIDDEN"], @@ -338,19 +322,20 @@ const boxingDayGame = { ], widths: [4, 12, 12, 8, 8], align: ["right", "left", "left", "left", "left"], - style: "single", // "single", "double", or "ascii" + style: "single", }, - "═══ ARCHIVED MESSAGES ═══", - "", + { text: "No new messages.", html: true, className: "info" }, { condition: "read_new_message", content: [ - " 1. [SENDER UNKNOWN] - For your eyes only", - { text: " (The number: 555-0237)", className: "info" }, + { + text: "Just the number... 555-0237...", + html: true, + className: "warning", + }, ], }, "", - { text: "No other messages.", className: "info" }, ], options: [{ text: "Back", next: "dark_tower_main" }], }, @@ -362,18 +347,49 @@ const boxingDayGame = { choice_immediate: { clear: true, content: [ - "Your fingers move before doubt can settle.", + { + type: "text", + text: "Your fingers move before doubt can settle.", + html: true, + className: "info", + }, "", { type: "typewriter", text: "ATH0", speed: 100 }, { type: "delay", ms: 400 }, { text: "NO CARRIER", className: "warning" }, { type: "delay", ms: 600 }, "", - "You disconnect from Dark Tower.", - "The silence of your room feels heavier now.", + { + text: "You disconnect from Dark Tower.", + html: true, + className: "info", + }, + { + text: "The silence of your room feels heavier now.", + html: true, + className: "info", + }, + "", + "", "", { type: "delay", ms: 500 }, - { text: "Something compels you forward.", className: "info" }, + //{ text: "Something compels you forward.", className: "info" }, + { + type: "typewriter", + text: "Something compels you forward...", + italic: true, + speed: 100, + className: "info", + }, + { type: "delay", ms: 1500 }, + { + type: "typewriter", + text: "...555-0237", + italic: true, + speed: 100, + className: "info", + }, + { type: "delay", ms: 2000 }, ], next: "dial_lighthouse", delay: 1000, @@ -395,28 +411,28 @@ const boxingDayGame = { "The number fades from memory.", "Just another piece of BBS spam, you tell yourself.", "", - { type: "delay", ms: 800 }, + { type: "delay", ms: 2000 }, "You browse Dark Tower for another hour.", - "Download some wallpapers. Chat about nothing.", + "Download some wallpapers.", "", - { type: "delay", ms: 800 }, - "At 11:47 PM, you disconnect.", + { type: "delay", ms: 2000 }, + "At 11:57 PM, you disconnect.", "", - { type: "delay", ms: 800 }, + { type: "delay", ms: 2000 }, "Five days later, the millennium arrives.", "Fireworks. Champagne. Relief.", "", { type: "delay", ms: 600 }, "Nothing happens.", "", - { type: "delay", ms: 1000 }, + { type: "delay", ms: 2000 }, { text: "Or does it?", className: "warning" }, "", - { type: "delay", ms: 800 }, + { type: "delay", ms: 2000 }, "You never find out what cascade.exe would have done.", "The lighthouse keeper's message was never meant for you.", "", - { type: "delay", ms: 600 }, + { type: "delay", ms: 1000 }, { text: "Perhaps that's for the best.", className: "info" }, "", { type: "delay", ms: 1000 }, @@ -448,53 +464,90 @@ const boxingDayGame = { // ========================================== browse_boards: { - title: "Message Boards", content: [ - "═══ DARK TOWER MESSAGE BOARDS ═══", - "", - " [1] General Discussion (47 new)", - " [2] Tech Support (12 new)", - // Archives conditionally shown { - condition: { not: "archives_deleted" }, - content: " [3] The Archives (3 new)", + type: "table", + title: "DARK TOWER / MESSAGE BOARDS", + headers: ["#", "NAME", "NEW MSG", "LAST"], + rows: [ + ["1", "General Discussion", "8", "24/12"], + ["2", "Tech Support", "1", "25/12"], + ["3", "File Updates", "3", "23/12"], + + // Display the archives or have them deleted + // depending on progress. + // Not sure if people will be able to go back from lighthouse to tower at this stage + // Leaving it in just incase I want to do this later... + { + condition: { not: "archives_deleted" }, + cells: ["4", "ARCHIVED", "-", "-"], + }, + { + condition: "archives_deleted", + cells: ["4", "", "-", "-"], + className: "error", + }, + ], + widths: [4, 20, 10, 8], + align: ["right", "left", "left", "left"], + style: "single", }, { condition: "archives_deleted", - content: { text: " [3] ", className: "error" }, + content: { + type: "typewriter", + italic: true, + text: "The archived messages are just... gone...", + speed: 80, + className: "info", + }, }, - " [4] File Announcements (8 new)", - "", ], prompt: "Select board:", options: [ { text: "General Discussion", next: "board_general" }, { text: "Tech Support", next: "board_tech" }, + { text: "File Updates", next: "board_files" }, { - text: "The Archives", + text: "ARCHIVED", next: "board_archives", condition: { not: "archives_deleted" }, }, - { text: "File Announcements", next: "board_files" }, { text: "Back to main menu", next: "dark_tower_main" }, ], }, board_general: { - title: "General Discussion", content: [ - "═══ GENERAL DISCUSSION ═══", - "", - " [HOT] Y2K Preparation Thread - 234 replies", - " [NEW] Anyone else getting weird messages? - 12 replies", - " [NEW] Happy Boxing Day everyone! - 8 replies", - " Best BBS games? - 45 replies", - " New user intro thread - 67 replies", - "", { - text: "The usual chatter. Nothing about lighthouses.", + type: "table", + title: "GENERAL DISCUSSION", + headers: ["#", "SUBJECT", "MSG", "LAST"], + rows: [ + ["1", "2K Preparation Thread", "243", "25/12"], + ["2", "Anyone else getting weird messages?", "3", "25/12"], + ["3", "Happy Boxing Day everyone!", "5", "25/12"], + ["4", "Best BBS games?", "43", "23/12"], + ["5", "New user intro thread", "67", "20/12"], + ], + widths: [4, 40, 6, 8], + align: ["right", "left", "right", "left"], + style: "single", + }, + { + text: "The usual chatter.", + italic: true, className: "info", }, + { + condition: "found_number", + content: { + type: "text", + italic: true, + text: "Nothing about lighthouses...", + className: "info", + }, + }, ], options: [ { text: "Read 'weird messages' thread", next: "thread_weird" }, @@ -503,14 +556,20 @@ const boxingDayGame = { }, thread_weird: { - title: "Thread: Anyone else getting weird messages?", + // title: "Thread: Anyone else getting weird messages?", content: [ - "═══ Anyone else getting weird messages? ═══", - "", - { text: "Posted by: Static_User", className: "info" }, - "Got a strange PM last night. No sender listed.", - "Just a phone number and something about a 'cascade'.", - "Probably spam, but creepy timing with Y2K coming up.", + { + type: "table", + title: "Anyone else getting weird messages?", + headers: ["FROM", "TO", "DATE"], + rows: [["Static_User", "All", "25/12/99"]], + widths: [20, 20, 10], + align: ["left", "left", "left"], + style: "single", + }, + " Got a strange PM last night. No sender listed.", + " Just a phone number and something about a 'cascade'.", + " Probably spam, but creepy timing with Y2K coming up.", "", "---", { text: "Reply from: NightWatchman [SYSOP]", className: "warning" }, @@ -519,14 +578,15 @@ const boxingDayGame = { "", "---", { text: "Reply from: [DELETED USER]", className: "error" }, - "[This post has been removed]", + "[This post cannot be accessed]", "", - { type: "delay", ms: 300 }, + { type: "delay", ms: 1000 }, { condition: "found_number", content: { - text: "You notice your message was similar...", - className: "warning", + text: "

You notice your message was similar...", + html: true, + className: "info", }, }, ], @@ -534,17 +594,26 @@ const boxingDayGame = { }, board_tech: { - title: "Tech Support", + // title: "Tech Support", content: [ - "═══ TECH SUPPORT ═══", - "", - " [STICKY] READ FIRST: Y2K Compliance Guide", - " [NEW] Modem dropping connection at midnight?", - " Best virus scanner for 1999?", - " How to increase download speeds", - "", + { + type: "table", + title: "TECH SUPPORT", + headers: ["#", "SUBJECT", "MSG", "LAST"], + rows: [ + ["1", "READ FIRST: Y2K Compliance Guide", "152", "25/12"], + ["2", "Modem dropping connection at midnight?", "3", "25/12"], + ["3", "How to increase download speeds", "98", "25/12"], + ["4", "We are migrating to TELNET/IP on 01/04/00", "429", "11/12"], + ["5", "Inputs not registering", "2", "29/11"], + ], + widths: [4, 45, 6, 8], + align: ["right", "left", "right", "left"], + style: "single", + }, { text: "Standard tech questions. Nothing unusual.", + italic: true, className: "info", }, ], @@ -668,46 +737,79 @@ const boxingDayGame = { }, board_files: { - title: "File Announcements", + // title: "File Announcements", content: [ - "═══ FILE ANNOUNCEMENTS ═══", - "", - " [NEW] Y2K_FIX.ZIP - Y2K compliance patches", - " [NEW] DOOM_WAD.ZIP - New Doom levels", - " FONTS99.ZIP - Cool fonts collection", - "", - { text: "Nothing about cascades here.", className: "info" }, + { + type: "table", + title: "FILE ANNOUNCEMENTS", + headers: ["#", "SUBJECT", "MSG", "LAST"], + rows: [ + ["1", "1001FONTS.ZIP - Font Collection", "1", "25/12"], + ["2", "Y2K_FIX.ZIP - Y2K compliance patches", "4", "23/12"], + ["3", "DOOM_WAD.ZIP - New Doom Levels", "3", "11/12"], + ["4", "BRUCE.JPEG - Just my dog :-)", "15", "20/11"], + ["5", "CATS.GIF - All your base are belong to us", "1", "01/11"], + ], + widths: [4, 45, 6, 8], + align: ["right", "left", "right", "left"], + style: "single", + }, + { + text: "New fonts... At last...", + html: true, + className: "info", + }, + { + text: "Can't get distracted just yet.", + html: true, + className: "info", + }, ], options: [{ text: "Back to boards", next: "browse_boards" }], }, dark_tower_files: { - title: "File Library", + //title: "File Library", content: [ - "═══ DARK TOWER FILE LIBRARY ═══", - "", - " /games - 234 files", - " /utils - 156 files", - " /images - 89 files", - " /music - 67 files", - "", - { text: "Standard BBS fare. Nothing unusual.", className: "info" }, + { + type: "table", + title: "FILE LIBRARY", + headers: ["#", "DIR", "QTY", "UPDATED"], + rows: [ + ["1", "/IMAGES", "234", "25/12"], + ["2", "/GAMES", "67", "12/12"], + ["3", "/MUSIC", "89", "30/11"], + ["4", "/UTILS", "156", "23/11"], + ["5", "/MISC", "13", "09/10"], + ], + widths: [4, 25, 6, 8], + align: ["right", "left", "right", "left"], + style: "single", + }, + { + text: "Standard BBS fare. Nothing unusual.", + html: true, + className: "info", + }, ], options: [{ text: "Back to main menu", next: "dark_tower_main" }], }, whos_online: { - title: "Who's Online", + //title: "Who's Online", content: [ - "═══ USERS ONLINE ═══", - "", - " 0BSERVER0 (you) - Main Menu", - " NightWatchman - SysOp Console", - " Static_User - Message Boards", - "", { - text: "Last login: Signal_Lost - March 23, 1999", - className: "warning", + type: "table", + title: "CONNECTED USERS", + headers: ["#", "USER", "LOC", "UPDATED"], + rows: [ + ["1", "0BSERVER0", "Main Menu", "10:54 PM"], + ["2", "Static_User", "Message Boards", "10:39 PM"], + ["3", "NightWatchman", "SysOp Console", "10:12 PM"], + ], + widths: [4, 15, 15, 8], + align: ["right", "left", "right", "left"], + style: "single", }, ], options: [{ text: "Back", next: "dark_tower_main" }], @@ -875,20 +977,27 @@ const boxingDayGame = { clear: true, content: [ { - type: "ansi", + type: "ascii", art: LIGHTHOUSE_HEADER, - className: "game-ansi-art center", + className: "game-ascii", }, "", - { text: "T H E L I G H T H O U S E", className: "game-scene-title" }, - { text: "Last updated: 12/24/1999 23:59:59", className: "info" }, - { text: "Users online: 1 (you)", className: "warning" }, + { text: "T H E L I G H T H O U S E", className: "center" }, + { text: "Last updated: 24/12/1999 23:59:59", className: "center" }, "", { condition: { not: "visited.lighthouse_main" }, content: [ - { text: "Something feels wrong here.", className: "warning" }, - { text: "The BBS feels... frozen. Abandoned.", className: "info" }, + { + text: "Something feels wrong here.", + italic: true, + className: "info", + }, + { + text: "The BBS feels... frozen. Abandoned.", + italic: true, + className: "info", + }, "", ], }, @@ -903,7 +1012,7 @@ const boxingDayGame = { ], }, ], - onEnter: [{ set: "visited.lighthouse_main", value: true }], + onAfterRender: [{ set: "visited.lighthouse_main", value: true }], prompt: "Navigate:", options: [ { text: "The Keeper's Log", next: "lighthouse_log" }, @@ -921,30 +1030,42 @@ const boxingDayGame = { }, lighthouse_log: { - title: "The Keeper's Log", + //title: "The Keeper's Log", content: [ "═══ THE KEEPER'S LOG ═══", "", - { text: "Entry 1 - November 3, 1998", className: "info" }, - "I've found something. In the static between radio stations.", - "Patterns. Structures. A language, maybe.", + { + text: "Entry 1 - November 3, 1998

", + html: true, + className: "info", + }, + " I've found something. In the static between radio stations.", + " Patterns. Structures. A language, maybe.", + { + text: "
Entry 7 - December 12, 1998

", + html: true, + className: "info", + }, + " The patterns are getting clearer. They want to be understood.", + " They want to SPREAD.", + { + text: "
Entry 15 - March 19, 1999

", + html: true, + className: "info", + }, + " CASCADE.EXE is complete. A translator. A carrier. A key.", + " When run at the right moment, it will open the door.", + { + text: "
Entry 23 - December 24, 1999

", + html: true, + className: "info", + }, + " The alignment approaches.", + " Seven days until the millennium.", + " I can hear them now. Always.", "", - { text: "Entry 7 - December 12, 1998", className: "info" }, - "The patterns are getting clearer.", - "They want to be understood.", - "They want to SPREAD.", - "", - { text: "Entry 15 - March 19, 1999", className: "info" }, - "CASCADE.EXE is complete.", - "A translator. A carrier. A key.", - "When run at the right moment, it will open the door.", - "", - { text: "Entry 23 - December 24, 1999", className: "warning" }, - "The alignment approaches.", - "Seven days until the millennium.", - "I can hear them now. Always.", - "", - { type: "typewriter", text: "They are beautiful.", speed: 60 }, + { type: "typewriter", text: " They are beautiful...", speed: 100 }, + { type: "delay", ms: 2000 }, ], options: [{ text: "Back", next: "lighthouse_main" }], }, @@ -1113,7 +1234,7 @@ const boxingDayGame = { ], onEnter: [ { set: "downloaded_cascade", value: true }, - { set: "archives_deleted", value: true }, // rm -rf effect + { set: "archives_deleted", value: true }, // Archives are removed ], next: "post_download", delay: 1500, @@ -1172,6 +1293,10 @@ const boxingDayGame = { choice_corrupted: { title: "Accessing SHADOW.DAT...", clear: true, + // sounds: [ + // { id: "static", url: "/assets/audio/static.mp3" }, + // { id: "glitch", url: "/assets/audio/glitch.mp3" }, + // ], content: [ { type: "typewriter", text: "ATTEMPTING TO READ FILE...", speed: 50 }, { type: "delay", ms: 1000 }, @@ -1183,6 +1308,8 @@ const boxingDayGame = { { text: "ERROR: ????????????????????", className: "error" }, { type: "delay", ms: 800 }, "", + // Play glitch sound effect + //{ type: "sound", id: "glitch", volume: 0.5 }, { type: "ascii", art: GLITCH_ART, className: "error" }, { type: "delay", ms: 1000 }, "", @@ -1190,6 +1317,8 @@ const boxingDayGame = { "", { type: "delay", ms: 600 }, { text: "A sound from your speakers.", className: "warning" }, + // Play eerie static with voice + //{ type: "sound", id: "static", volume: 0.4, duration: 3000, fade: true }, { text: "A voice, maybe. Or static shaped like words:", className: "info", diff --git a/assets/sass/partials/_terminal.scss b/assets/sass/partials/_terminal.scss index b9916b2..72afe32 100644 --- a/assets/sass/partials/_terminal.scss +++ b/assets/sass/partials/_terminal.scss @@ -161,3 +161,11 @@ .typewriter-line { display: inline; } + +.typewriter-bold { + font-weight: bold; +} + +.typewriter-italic { + font-style: italic; +} diff --git a/content/blog/2026-01-25-week-5-back-to-some-gamedev/index.md b/content/blog/2026-01-25-week-5-back-to-some-gamedev/index.md new file mode 100644 index 0000000..830ab9d --- /dev/null +++ b/content/blog/2026-01-25-week-5-back-to-some-gamedev/index.md @@ -0,0 +1,27 @@ +--- +title: "Week 5 - Back to Some Gamedev" +date: 2026-01-25 +tags: + - weeknote + - weekly update +draft: false +--- + +- Emojii +- Memeojii + +## Links I Found Interesting + +- [AI Company Logos That Look Like Buttholes](https://velvetshark.com/ai-company-logos-that-look-like-buttholes) - Pretty self explanatory if you ask me. + +## Music + +If you like + +{{< youtube MxekyGtqcNE >}} + +## Next Week + +Not necessary + +Until next week! diff --git a/content/buttons/222-2.gif b/content/buttons/222-2.gif new file mode 100644 index 0000000..7aa5614 Binary files /dev/null and b/content/buttons/222-2.gif differ diff --git a/static/audio/modem-connect.mp3 b/static/audio/modem-connect.mp3 new file mode 100644 index 0000000..2d5bdf4 Binary files /dev/null and b/static/audio/modem-connect.mp3 differ