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