From 033d0ffcb777a4e7c8bc5f5f1876d7064b8536cf3a20b06c2eec74b741ee35ec Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 19 May 2026 01:41:07 +0200 Subject: [PATCH] Initial commit --- central_init_restrictions_checker.py | 0 commands.py | 0 config/configuration.ini | 54 + configloader.py | 0 cryptographic_methods.py | 0 db_handler.py | 0 db_init.py | 0 main.py | 62 ++ msic_bot.py | 70 ++ resources/password_access_list.txt | 0 roadmap.txt | 8 + utils.py | 0 web_files/files.js | 0 web_files/index.html | 286 ++++++ web_files/index.js | 666 +++++++++++++ web_files/resources.js | 0 web_files/style.css | 1386 ++++++++++++++++++++++++++ webhandler.py | 0 webloader.py | 0 19 files changed, 2532 insertions(+) create mode 100644 central_init_restrictions_checker.py create mode 100644 commands.py create mode 100644 config/configuration.ini create mode 100644 configloader.py create mode 100644 cryptographic_methods.py create mode 100644 db_handler.py create mode 100644 db_init.py create mode 100644 main.py create mode 100644 msic_bot.py create mode 100644 resources/password_access_list.txt create mode 100644 roadmap.txt create mode 100644 utils.py create mode 100644 web_files/files.js create mode 100644 web_files/index.html create mode 100644 web_files/index.js create mode 100644 web_files/resources.js create mode 100644 web_files/style.css create mode 100644 webhandler.py create mode 100644 webloader.py diff --git a/central_init_restrictions_checker.py b/central_init_restrictions_checker.py new file mode 100644 index 0000000..473a0f4 diff --git a/commands.py b/commands.py new file mode 100644 index 0000000..473a0f4 diff --git a/config/configuration.ini b/config/configuration.ini new file mode 100644 index 0000000..0111142 --- /dev/null +++ b/config/configuration.ini @@ -0,0 +1,54 @@ +# ========================= +# CONFIGURATION +# ========================= + + + +[DIRECT_SETTINGS] +BOT_USERNAME = "blood_linksbot" # the username of your Telegram bot (without @) +BOT_TOKEN = "8649631256:AAHDQgDhpd8ymxACdNni1HomLuMzcm_W2Lg" # the token for your Telegram bot (get it from @BotFather), cannot be changed using commands and completely hidden for security reasons +TELEGRAM_PUBLIC_GROUP_ID = -1003571704137 # the ID of the public group where user-uploaded content will be exposed (if chosen) +TELEGRAM_ADMINISTRATION_GROUP_ID = -1003737694563 # the ID of the private group where admin-uploaded content will be reviewed/generated +OWNER_TELEGRAM_USER_ID = 8615679055 # the Telegram user ID of the bot owner (for managing critical settings), cannot be changed using commands + + +[BOT_SETTINGS] +ALLOW_LINKS_TO_TEXT = true # whether to allow users to link their text messages (true = feature enabled & allowed, false = feature disabled & not allowed) +ALLOW_LINKS_TO_MEDIA = true # whether to allow users to link their media from messages personally and receive custom links (true = feature enabled & allowed, false = feature disabled & not allowed) +WARNING_MESSAGE_ENABLED = true # whether to require user to confirm rules/ usage plan before interacting with the bot, true = enabled, false = disabled +MAXIMUM_CHAR_LIMIT_FOR_TEXT_MSGS = 9000 # the maximum character limit for text messages that users can link, set to a reasonable number to prevent abuse (e.g., 12000 characters) +REPORT_FEATURE_ENABLED = true # whether to allow users to report inappropriate content, true = enabled, false = disabled +DELETE_TELEGRAM_ACTIONS = true # whether to delete all basic telegram report messages/warnings such as user left, joined, message x was pinned, ect. in the groups to keep them clean, true = enabled, false = disabled +ALLOW_USERS_TO_DELETE_CONTENT = true # whether to allow users to delete their own content/links, making all links/content ID's immediately ineffective, true = enabled, false = disabled +ACTUALLY_DELETE_CONTENT = false # THIS OPTION IS NOT SHOWN; whether to actually the content that gets burned, deleted by users or administrators, true = fully deletes all media if that was the intended action & only saves content after accepting submissions/when neccessary, false = saves ALL contents, regardless whether they were accepted by administrators or not & does not delete them under all circumstances +DELETE_SUBMISSIONS_AFTER_REVIEW = moderated # whether to delete the original Telegram messages of user submissions after they have been reviewed by administrators, true = enabled, false = disabled, moderated = only delete if the content was moderated directly, i.e. the user banned for posting it +ALLOW_CUSTOM_PASSWORDS_FOR_LINKS = false # whether to allow users to choose their own custom passwords for accessing their content links, true = enabled, false = disabled (if disabled, the bot will generate random passwords for all links) +# if chosen direct, a randomized string is generated and directly embedded within URL for automatic loading, if chosen external the user has to enter the password given manually before accessing contents, to avoid long, complex strings uses a wordlist of possible passwords and chooses randomly from it +PASSWORD_METHOD_TYPE = "direct" # options: "direct", "external" +EXTERNAL_PASSWORD_LIST_TXT = "./resources/password_access_list.txt" + + +[STORAGE_SETTINGS] +TEXT_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json" +BAN_LOG_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json" +FILE_HASHES_KEY_WRAPPED_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json" +PREVIOUS_USERS_FOR_ADVERTISEMENTS_STORAGE_TYPE = "sqlite3" # options: "sqlite3", "json" +USER_UPLOADED_CONTENT_EXPOSED_METHOD = "web" # options: "web", "telegram", "both" +ADMIN_UPLOADED_CONTENT_EXPOSED_METHOD = "web" # options: "web", "telegram", "both" +# DO NOT ENTER ANYTHING HERE MANUALLY, SOFTWARE SETS ENCRYPTION KEYS AUTOMATICALLY +EPHEMERAL_AES_ENVELOPE_KEY = "" # This feature uses xchacha20-poly1305 alongside AES-KW (RFC 5649) for wrapping chacha keys - randomized wrapper key to securely encrypt/decrypt every file/textblob from traffic, cannot be changed using commands and completely hidden for security reasons +SUPPORTED_MEDIA_TYPES_STRING = "image, video, audio, document" # the media types that are allowed to be linked by users, will show up as 'X is not an allowed media type, only the following media types are allowed: X, Y, Z' if user tries to link a media type that is not in this list, shown but cannot be changed using commands +DATABASE_FOLDER_PATH = "./databases" # the folder path where all databases (if using sqlite3) or json files (if using json) will be stored, make sure the bot has read/write permissions for this folder, shown yet cannot be changed using commands + + +[WEB_SETTINGS] +PORT_EXPOSED = 9465 # the port that will be exposed for the web server (if enabled) +GENERATE_SECURE_KEY_FOR_CONTENT_LINKS = true # whether to generate secure, random keys for accessing web content instead of allowing direct API access to anyone, true = enabled, false = disabled +ALLOW_COMMENTS_ON_WEB_CONTENT_PAGES = true # whether to allow users to comment anonymously (cloudflare-integration important!) on content pages on the web interface, true = enabled, false = disabled + + +[MISC] +DEVELOPER_TELEGRAM_USERNAME = "forgecadrape" # credits for making this bot (plain Username without @), cannot be changed using commands +FORBIDDEN_CONTENT_TYPES_STRING = "CP, necriphilia, ect." # will shows up as 'X is strictly forbidden and leads to permanent deletion of links as well as exclusion from bot.' +MEDIA_TOPIC_FOR_BOT = "gore" # the topic that is generally seen and accepted by administrators for media generation, crucial for explicitly stating to new users which content will likely get approved. +CONTACT_TELEGERAM_LINK = "harmfulmeowbot?start=submit" # can be either BOT_NAME?start=submit OR TELEGRAM_USERNAME \ No newline at end of file diff --git a/configloader.py b/configloader.py new file mode 100644 index 0000000..473a0f4 diff --git a/cryptographic_methods.py b/cryptographic_methods.py new file mode 100644 index 0000000..473a0f4 diff --git a/db_handler.py b/db_handler.py new file mode 100644 index 0000000..473a0f4 diff --git a/db_init.py b/db_init.py new file mode 100644 index 0000000..473a0f4 diff --git a/main.py b/main.py new file mode 100644 index 0000000..ef61247 --- /dev/null +++ b/main.py @@ -0,0 +1,62 @@ +from aiogram import Bot, Dispatcher, F +from aiogram.enums import ParseMode +from aiogram.filters import Command +from aiogram.types import Message +from aiogram.client.default import DefaultBotProperties +import asyncio + +bot = Bot( + token=BOT_TOKEN, + default=DefaultBotProperties(parse_mode=ParseMode.HTML) +) + +dp = Dispatcher() + +def is_admin(user_id: int) -> bool: + return user_id in ADMINS + +@dp.message(Command("help")) +async def help_command(message: Message): + + if not is_admin(message.from_user.id): + return + + help_text = """ +Bold text + +Italic text + +Underlined text + +Strikethrough text + +Spoiler text + +Mono font / inline code + +
+Multi-line
+mono block
+
+ +Bold + Italic + +Line 1 +Line 2 +Line 3 + +Clickable URL +""" + + await message.answer(help_text) + +@dp.message(Command("start")) +async def start_command(message: Message): + await message.answer("Bot is running.") + +async def main(): + print("Bot started.") + await dp.start_polling(bot) + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/msic_bot.py b/msic_bot.py new file mode 100644 index 0000000..c862160 --- /dev/null +++ b/msic_bot.py @@ -0,0 +1,70 @@ +HELP_MESSAGE = f"""t.me/{BOT_USERNAME} + +--- ADMINISTRATION CMDS --- +/add_admin ID or reply to msg - adds an administrator which provides them with the ability of interacting with the bot in administrative ways +/rm_admin ID or reply to msg - removes an administrator which restricts them from interacting with the bot in any administrative way +/add_coown ID or reply to msg - adds a co-owner which have nearly full permissions over the bot and also the ability to edit configurations +/rm_coown ID or reply to msg - removes a co-owner which have nearly full permissions over the bot and also the ability to edit configurations + +(An Owner/Co-Owner can add other Administrators.) + +--- LINK/USE CMDS --- +/create_link reply to msg with vids/pics /submit in dms and approve by yourself - generates a simple link users can click one to view your uploaded media. +/create_full_link - takes all attachments of this channel and generates link to it, use carefully, high overload potential + pulls all media, even if not gore. +/adv_group TG_INV_LINK - sends a telegram group link provided to all users who previously interacted with the bot +/help - shows this msg + +--- LINK MISC CMDS --- +/show_links - shows all links +/del_link LINK_ID - deletes a link by link id from /show_links +/push_web WEB_URL ID or reply to msg - adds content replied to/referenced to the already existing web accessor provided + +--- CONFIG CMDS --- +/config show - shows the current configuration alongside keys & all values except for the bot token, which is hidden for security reasons +/config reload - reloads the configuration file, i.e. after changes useful +/config set KEY VALUE - sets a value for a certain key + +--- EXPLAINATION --- +Any user can submit/upload gore or any other type of content via dms (t.me/{BOT_USERNAME}?start=submit), it gets sent here. if you click Approve & send group, the link to the media gets generated and automatically sent to your public channel. + +If you click approve, the link to the media gets generated and you can share it. + +If you click deny, the submission gets deleted and no link is generated. + +If you click deny & ban, the submission gets deleted, the user gets banned from using the bot again & also banned from all channels. + +Bot programmed by @{DEVELOPER_TELEGRAM_USERNAME}.""" + + +WARNING_MESSAGE = f"""The topic of this Bot is {MEDIA_TOPIC_FOR_BOT.upper()}. Only submit media regarding that topic. + +
+{FORBIDDEN_CONTENT_TYPES_STRING} is strictly forbidden and leads to permanent deletion of links as well as exclusion from bot. +
+ +Enter content ID -> enter your content ID/link and the bot will upload all the videos/photos/texts to you in this chat, exposing the redirected hidden content + +& + +Create Link To Media -> upload your own content and generate link to it + +& + +Submit Media to Admins -> upload your own content and have admins review it, they will take action depending on what and how you submitted. + +& + +Create Link To Text -> Asks you to share contents (max. {MAXIMUM_CHAR_LIMIT_FOR_TEXT_MSGS} chars), then asks after how many views/usages your link shall get burned/data deleted. + +& + +My links -> shows all links you have created & their management options + + +Do you understand that by clicking proceed, you will see multiple follow up options, and possibly engage with extreme content?""" + +START_MESSAGE = f"""Hey, this bot is used to upload, store and create links to all sorts of cruel media. Please choose from options below. + +The prior message explains what the below options do pretty well. + +For contacting administrators, please use t.me/{CONTACT_TELEGERAM_LINK}.""" \ No newline at end of file diff --git a/resources/password_access_list.txt b/resources/password_access_list.txt new file mode 100644 index 0000000..473a0f4 diff --git a/roadmap.txt b/roadmap.txt new file mode 100644 index 0000000..9f42495 --- /dev/null +++ b/roadmap.txt @@ -0,0 +1,8 @@ +1. Init the basic log +2. Init all the databases if not existing, add schemes, tables ect. +3. Init/online web server if used by users/admins +4. Start the telegram bot + +Signoff/web credits +"Productions by t.me/forgecadrape +Business inquiries 056834db96cedde6012da9d5402c683c0eb260ed866da145a8f86e7f329bf77222 on Session" diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..473a0f4 diff --git a/web_files/files.js b/web_files/files.js new file mode 100644 index 0000000..473a0f4 diff --git a/web_files/index.html b/web_files/index.html new file mode 100644 index 0000000..f6a3146 --- /dev/null +++ b/web_files/index.html @@ -0,0 +1,286 @@ + + + + + + CANNIBAL GIRLS // ARCHIVE + + + + + + + +
+ + +
+ + +
+ + +
+
+
+
+ + SYSTEM ONLINE +
+
+
+ +
+

CANNIBAL GIRLS

+
+ + + +
+

ARCHIVE TERMINAL v2.4.1

+ + +
+ + +
+
+ + + + + +
+ +
+
+

CANNIBAL_GIRLS

+ // + ARCHIVE_BROWSER +
+
+
+ + FILES: + 0 + + + SIZE: + 0 GB + +
+
+
+
+ + +
+ + + + +
+ +
+
+
+ + +
+
+ SORT: + +
+
+
+ +
+
+ + +
+
+ NAME + TYPE + SIZE + DATE +
+
+
+
+ + + +
+ + +
+ + + +
+
+ + + + + + + diff --git a/web_files/index.js b/web_files/index.js new file mode 100644 index 0000000..c8ba749 --- /dev/null +++ b/web_files/index.js @@ -0,0 +1,666 @@ +/** + * CANNIBAL GIRLS - ARCHIVE TERMINAL + * Main Application Logic + */ + +(function() { + 'use strict'; + + // ======================================== + // CONFIGURATION + // ======================================== + const CONFIG = { + loadDelayMin: 4000, + loadDelayMax: 9000, + audioVolume: 0.15, + audioPath: 'audio/', + terminalLines: [ + { text: 'Mounting storage volumes...', type: 'normal', delay: 0 }, + { text: 'Checking filesystem integrity...', type: 'normal', delay: 300 }, + { text: 'Scanning /archive/repository...', type: 'normal', delay: 600 }, + { text: 'Found 247 indexed files', type: 'success', delay: 1200 }, + { text: 'Verifying checksums...', type: 'normal', delay: 1500 }, + { text: 'Warning: 3 files flagged for review', type: 'warning', delay: 2200 }, + { text: 'Rebuilding file index...', type: 'normal', delay: 2500 }, + { text: 'Optimizing cache...', type: 'normal', delay: 3200 }, + { text: 'Archive mount successful', type: 'success', delay: 4000 }, + { text: 'Ready for access', type: 'success', delay: 4500 } + ] + }; + + // ======================================== + // DEMO FILE DATA + // ======================================== + const DEMO_FILES = [ + { id: 1, name: 'project_venus_raw.mp4', type: 'video', size: 2847563210, date: '2024-01-15T14:23:00', checksum: 'a3f7c9d2e1b4f5a6c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1' }, + { id: 2, name: 'archive_segment_07.mkv', type: 'video', size: 1456789012, date: '2024-01-14T09:15:00', checksum: 'b4c8d0e3f2a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c2' }, + { id: 3, name: 'nocturne_stills_set.zip', type: 'archive', size: 456782345, date: '2024-01-13T16:45:00', checksum: 'c5d9e1f4a3b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d3' }, + { id: 4, name: 'soundscape_01.flac', type: 'audio', size: 124567890, date: '2024-01-12T11:30:00', checksum: 'd6e0f2a5b4c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e4' }, + { id: 5, name: 'behind_the_scenes_03.jpg', type: 'image', size: 8945678, date: '2024-01-11T08:20:00', checksum: 'e7f1a3b6c5d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f5' }, + { id: 6, name: 'production_notes_v2.pdf', type: 'document', size: 2345678, date: '2024-01-10T19:00:00', checksum: 'f8a2b4c7d6e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a6' }, + { id: 7, name: 'raw_footage_reel_12.mp4', type: 'video', size: 5678901234, date: '2024-01-09T13:10:00', checksum: 'a9b3c5d7e8f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7' }, + { id: 8, name: 'color_grade_lut.cube', type: 'document', size: 45678, date: '2024-01-08T10:45:00', checksum: 'b0c4d6e8f9a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8' }, + { id: 9, name: 'ambient_collection.zip', type: 'archive', size: 345678901, date: '2024-01-07T15:30:00', checksum: 'c1d5e7f9a0b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9' }, + { id: 10, name: 'title_sequence_v3.mov', type: 'video', size: 1234567890, date: '2024-01-06T07:00:00', checksum: 'd2e6f8a0b1c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0' }, + { id: 11, name: 'location_scout_photos.zip', type: 'archive', size: 156789012, date: '2024-01-05T12:15:00', checksum: 'e3f7a9b1c2d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1' }, + { id: 12, name: 'score_master_24bit.wav', type: 'audio', size: 890123456, date: '2024-01-04T18:30:00', checksum: 'f4a8b0c2d3e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2' }, + { id: 13, name: 'character_studies_01.png', type: 'image', size: 45678901, date: '2024-01-03T09:45:00', checksum: 'a5b9c1d3e4f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3' }, + { id: 14, name: 'edit_timeline_v4.prproj', type: 'document', size: 12345678, date: '2024-01-02T14:00:00', checksum: 'b6c0d2e4f5a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4' }, + { id: 15, name: 'promo_cut_30sec.mp4', type: 'video', size: 678901234, date: '2024-01-01T11:20:00', checksum: 'c7d1e3f5a6b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5' } + ]; + + // ======================================== + // STATE + // ======================================== + let state = { + files: [], + filteredFiles: [], + selectedFile: null, + currentView: 'list', + currentFilter: 'all', + currentSort: 'date-desc', + searchQuery: '', + uptime: 0, + audioContext: null + }; + + // ======================================== + // DOM REFERENCES + // ======================================== + const els = {}; + + function cacheElements() { + els.loadingScreen = document.getElementById('loading-screen'); + els.loadingTitle = document.getElementById('loading-title'); + els.loadingDatetime = document.getElementById('loading-datetime'); + els.loadBtn = document.getElementById('load-files-btn'); + els.loadingPhase = document.getElementById('loading-phase'); + els.terminalOutput = document.getElementById('terminal-output'); + els.progressText = document.getElementById('progress-text'); + els.progressPercent = document.getElementById('progress-percent'); + els.progressFill = document.getElementById('progress-fill'); + els.archiveBrowser = document.getElementById('archive-browser'); + els.fileList = document.getElementById('file-list'); + els.fileListHeader = document.getElementById('file-list-header'); + els.sidebarMenu = document.getElementById('sidebar-menu'); + els.sortSelect = document.getElementById('sort-select'); + els.searchInput = document.getElementById('search-input'); + els.previewPane = document.getElementById('preview-pane'); + els.previewContent = document.getElementById('preview-content'); + els.previewClose = document.getElementById('preview-close'); + els.previewMeta = document.getElementById('preview-meta'); + els.headerTime = document.getElementById('header-time'); + els.uptime = document.getElementById('uptime'); + els.fileCount = document.getElementById('file-count'); + els.totalSize = document.getElementById('total-size'); + els.footerStatus = document.getElementById('footer-status'); + els.bgAudio = document.getElementById('bg-audio'); + els.actionDownload = document.getElementById('action-download'); + els.actionCopy = document.getElementById('action-copy'); + } + + // ======================================== + // UTILITIES + // ======================================== + function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + } + + function formatDate(dateString) { + const date = new Date(dateString); + return date.toISOString().slice(0, 19).replace('T', ' '); + } + + function formatDateShort(dateString) { + const date = new Date(dateString); + return date.toISOString().slice(0, 10); + } + + function getFileIcon(type) { + const icons = { + video: '▶', + audio: '♬', + image: '■', + document: '☰', + archive: '■' + }; + return icons[type] || '■'; + } + + function getRandomDelay() { + return Math.floor(Math.random() * (CONFIG.loadDelayMax - CONFIG.loadDelayMin + 1)) + CONFIG.loadDelayMin; + } + + function generateChecksum() { + const chars = 'abcdef0123456789'; + let result = ''; + for (let i = 0; i < 64; i++) { + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; + } + + // ======================================== + // TEXT SCRAMBLE EFFECT + // ======================================== + class TextScramble { + constructor(el) { + this.el = el; + this.chars = '!<>-_\\/[]{}--=+*^?#________'; + this.update = this.update.bind(this); + } + + setText(newText) { + const oldText = this.el.innerText; + const length = Math.max(oldText.length, newText.length); + const promise = new Promise(resolve => this.resolve = resolve); + this.queue = []; + for (let i = 0; i < length; i++) { + const from = oldText[i] || ''; + const to = newText[i] || ''; + const start = Math.floor(Math.random() * 40); + const end = start + Math.floor(Math.random() * 40); + this.queue.push({ from, to, start, end }); + } + cancelAnimationFrame(this.frameRequest); + this.frame = 0; + this.update(); + return promise; + } + + update() { + let output = ''; + let complete = 0; + for (let i = 0, n = this.queue.length; i < n; i++) { + let { from, to, start, end, char } = this.queue[i]; + if (this.frame >= end) { + complete++; + output += to; + } else if (this.frame >= start) { + if (!char || Math.random() < 0.28) { + char = this.randomChar(); + this.queue[i].char = char; + } + output += `${char}`; + } else { + output += from; + } + } + this.el.innerHTML = output; + if (complete === this.queue.length) { + this.resolve(); + } else { + this.frameRequest = requestAnimationFrame(this.update); + this.frame++; + } + } + + randomChar() { + return this.chars[Math.floor(Math.random() * this.chars.length)]; + } + } + + // ======================================== + // AUDIO MANAGEMENT + // ======================================== + async function getAudioFiles() { + // In a real scenario, this would fetch from the server + // For now, we'll try common audio filenames or return empty + const commonNames = ['ambient.mp3', 'noise.mp3', 'static.mp3', 'drone.mp3', 'hum.mp3']; + return commonNames.map(name => CONFIG.audioPath + name); + } + + async function playRandomAudio() { + try { + const audioFiles = await getAudioFiles(); + if (audioFiles.length === 0) { + console.log('No audio files found in', CONFIG.audioPath); + return; + } + + const randomFile = audioFiles[Math.floor(Math.random() * audioFiles.length)]; + els.bgAudio.src = randomFile; + els.bgAudio.volume = CONFIG.audioVolume; + + const playPromise = els.bgAudio.play(); + if (playPromise !== undefined) { + playPromise.catch(err => { + console.log('Audio playback failed:', err.message); + }); + } + } catch (err) { + console.log('Audio error:', err); + } + } + + function stopAudio() { + els.bgAudio.pause(); + els.bgAudio.currentTime = 0; + } + + // ======================================== + // TERMINAL OUTPUT + // ======================================== + function addTerminalLine(text, type = 'normal') { + const line = document.createElement('div'); + line.className = 'terminal-line'; + + const prompt = document.createElement('span'); + prompt.className = 'line-prompt'; + prompt.textContent = '>'; + + const content = document.createElement('span'); + content.className = `line-content ${type}`; + content.textContent = text; + + line.appendChild(prompt); + line.appendChild(content); + els.terminalOutput.appendChild(line); + + // Auto-scroll + els.terminalOutput.scrollTop = els.terminalOutput.scrollHeight; + } + + // ======================================== + // LOADING SEQUENCE + // ======================================== + async function startLoadingSequence() { + const delay = getRandomDelay(); + + // Hide loading screen, show loading phase + els.loadBtn.disabled = true; + els.loadBtn.innerHTML = ''; + + setTimeout(() => { + els.loadingScreen.classList.add('hidden'); + els.loadingPhase.classList.remove('hidden'); + + // Start audio + playRandomAudio(); + + // Start terminal output + let currentTime = 0; + CONFIG.terminalLines.forEach(line => { + setTimeout(() => { + addTerminalLine(line.text, line.type); + }, line.delay); + }); + + // Progress bar + const startTime = Date.now(); + const updateProgress = () => { + const elapsed = Date.now() - startTime; + const progress = Math.min((elapsed / delay) * 100, 100); + + els.progressFill.style.width = progress + '%'; + els.progressPercent.textContent = Math.floor(progress) + '%'; + + // Update progress text based on percentage + if (progress < 20) els.progressText.textContent = 'MOUNTING VOLUMES...'; + else if (progress < 40) els.progressText.textContent = 'VERIFYING CHECKSUMS...'; + else if (progress < 60) els.progressText.textContent = 'INDEXING FILES...'; + else if (progress < 80) els.progressText.textContent = 'OPTIMIZING CACHE...'; + else if (progress < 100) els.progressText.textContent = 'FINALIZING...'; + else els.progressText.textContent = 'COMPLETE'; + + if (progress < 100) { + requestAnimationFrame(updateProgress); + } else { + setTimeout(finishLoading, 500); + } + }; + + requestAnimationFrame(updateProgress); + }, 300); + } + + function finishLoading() { + stopAudio(); + els.loadingPhase.classList.add('hidden'); + els.archiveBrowser.classList.remove('hidden'); + els.archiveBrowser.classList.add('active'); + + // Initialize archive + initArchive(); + } + + // ======================================== + // ARCHIVE INITIALIZATION + // ======================================== + function initArchive() { + state.files = DEMO_FILES.map(f => ({ + ...f, + checksum: f.checksum || generateChecksum() + })); + + updateCounts(); + filterAndSortFiles(); + renderFiles(); + updateStats(); + + // Start uptime counter + setInterval(() => { + state.uptime++; + updateUptime(); + }, 1000); + } + + function updateCounts() { + const counts = { + all: state.files.length, + video: state.files.filter(f => f.type === 'video').length, + image: state.files.filter(f => f.type === 'image').length, + audio: state.files.filter(f => f.type === 'audio').length, + document: state.files.filter(f => f.type === 'document').length, + archive: state.files.filter(f => f.type === 'archive').length + }; + + Object.keys(counts).forEach(key => { + const el = document.getElementById(`count-${key}`); + if (el) el.textContent = counts[key]; + }); + } + + function updateStats() { + els.fileCount.textContent = state.filteredFiles.length; + const totalBytes = state.filteredFiles.reduce((sum, f) => sum + f.size, 0); + els.totalSize.textContent = formatBytes(totalBytes); + } + + function updateUptime() { + const hours = Math.floor(state.uptime / 3600).toString().padStart(2, '0'); + const minutes = Math.floor((state.uptime % 3600) / 60).toString().padStart(2, '0'); + const seconds = (state.uptime % 60).toString().padStart(2, '0'); + els.uptime.textContent = `${hours}:${minutes}:${seconds}`; + } + + // ======================================== + // FILE FILTERING & SORTING + // ======================================== + function filterAndSortFiles() { + let files = [...state.files]; + + // Apply filter + if (state.currentFilter !== 'all') { + files = files.filter(f => f.type === state.currentFilter); + } + + // Apply search + if (state.searchQuery) { + const query = state.searchQuery.toLowerCase(); + files = files.filter(f => f.name.toLowerCase().includes(query)); + } + + // Apply sort + files.sort((a, b) => { + switch (state.currentSort) { + case 'name-asc': + return a.name.localeCompare(b.name); + case 'name-desc': + return b.name.localeCompare(a.name); + case 'date-desc': + return new Date(b.date) - new Date(a.date); + case 'date-asc': + return new Date(a.date) - new Date(b.date); + case 'size-desc': + return b.size - a.size; + case 'size-asc': + return a.size - b.size; + case 'type': + return a.type.localeCompare(b.type) || a.name.localeCompare(b.name); + default: + return 0; + } + }); + + state.filteredFiles = files; + updateStats(); + } + + // ======================================== + // FILE RENDERING + // ======================================== + function renderFiles() { + els.fileList.innerHTML = ''; + + if (state.filteredFiles.length === 0) { + const empty = document.createElement('div'); + empty.className = 'file-item'; + empty.innerHTML = 'NO FILES FOUND'; + els.fileList.appendChild(empty); + return; + } + + state.filteredFiles.forEach((file, index) => { + const item = document.createElement('div'); + item.className = 'file-item'; + if (state.selectedFile && state.selectedFile.id === file.id) { + item.classList.add('selected'); + } + + // Add staggered animation + item.style.animation = `fadeInUp 0.3s ease-out ${index * 0.03}s both`; + + item.innerHTML = ` +
+ ${getFileIcon(file.type)} + ${file.name} +
+ ${file.type} + ${formatBytes(file.size)} + ${formatDateShort(file.date)} + `; + + item.addEventListener('click', () => selectFile(file)); + els.fileList.appendChild(item); + }); + } + + // ======================================== + // FILE SELECTION & PREVIEW + // ======================================== + function selectFile(file) { + state.selectedFile = file; + + // Update selection in list + document.querySelectorAll('.file-item').forEach(item => { + item.classList.remove('selected'); + }); + + const selectedEl = Array.from(els.fileList.children).find(el => + el.querySelector('.file-name')?.textContent === file.name + ); + if (selectedEl) selectedEl.classList.add('selected'); + + // Update preview + updatePreview(file); + + // Update footer status + els.footerStatus.textContent = `SELECTED: ${file.name}`; + } + + function updatePreview(file) { + // Update preview content + els.previewContent.innerHTML = ` +
+ ${getFileIcon(file.type)} + ${file.type.toUpperCase()} FILE +
+ `; + + // Update meta + document.getElementById('meta-filename').textContent = file.name; + document.getElementById('meta-type').textContent = file.type.toUpperCase(); + document.getElementById('meta-size').textContent = formatBytes(file.size); + document.getElementById('meta-created').textContent = formatDate(file.date); + document.getElementById('meta-modified').textContent = formatDate(file.date); + document.getElementById('meta-checksum').textContent = file.checksum; + } + + function closePreview() { + state.selectedFile = null; + document.querySelectorAll('.file-item').forEach(item => { + item.classList.remove('selected'); + }); + els.previewContent.innerHTML = ` +
+ + SELECT A FILE TO PREVIEW +
+ `; + document.getElementById('meta-filename').textContent = '---'; + document.getElementById('meta-type').textContent = '---'; + document.getElementById('meta-size').textContent = '---'; + document.getElementById('meta-created').textContent = '---'; + document.getElementById('meta-modified').textContent = '---'; + document.getElementById('meta-checksum').textContent = '---'; + els.footerStatus.textContent = 'READY'; + } + + // ======================================== + // VIEW SWITCHING + // ======================================== + function switchView(view) { + state.currentView = view; + + // Update buttons + document.querySelectorAll('.view-btn').forEach(btn => { + btn.classList.toggle('active', btn.dataset.view === view); + }); + + // Update list class + if (view === 'grid') { + els.fileList.classList.add('grid-view'); + els.fileListHeader.style.display = 'none'; + } else { + els.fileList.classList.remove('grid-view'); + els.fileListHeader.style.display = 'grid'; + } + + renderFiles(); + } + + // ======================================== + // EVENT LISTENERS + // ======================================== + function bindEvents() { + // Load button + els.loadBtn.addEventListener('click', startLoadingSequence); + + // Sidebar filters + els.sidebarMenu.addEventListener('click', (e) => { + const item = e.target.closest('.sidebar-item'); + if (!item) return; + + document.querySelectorAll('.sidebar-item').forEach(i => i.classList.remove('active')); + item.classList.add('active'); + + state.currentFilter = item.dataset.filter; + filterAndSortFiles(); + renderFiles(); + }); + + // View switcher + document.querySelectorAll('.view-btn').forEach(btn => { + btn.addEventListener('click', () => switchView(btn.dataset.view)); + }); + + // Sort + els.sortSelect.addEventListener('change', (e) => { + state.currentSort = e.target.value; + filterAndSortFiles(); + renderFiles(); + }); + + // Search + els.searchInput.addEventListener('input', (e) => { + state.searchQuery = e.target.value; + filterAndSortFiles(); + renderFiles(); + }); + + // Preview close + els.previewClose.addEventListener('click', closePreview); + + // Download button + els.actionDownload.addEventListener('click', () => { + if (!state.selectedFile) return; + + const blob = new Blob([`File: ${state.selectedFile.name}\nSize: ${formatBytes(state.selectedFile.size)}\nChecksum: ${state.selectedFile.checksum}`], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = state.selectedFile.name + '.info.txt'; + a.click(); + URL.revokeObjectURL(url); + + els.footerStatus.textContent = 'DOWNLOADED INFO FILE'; + setTimeout(() => { + els.footerStatus.textContent = 'READY'; + }, 2000); + }); + + // Copy hash button + els.actionCopy.addEventListener('click', () => { + if (!state.selectedFile) return; + + navigator.clipboard.writeText(state.selectedFile.checksum).then(() => { + els.footerStatus.textContent = 'CHECKSUM COPIED TO CLIPBOARD'; + setTimeout(() => { + els.footerStatus.textContent = 'READY'; + }, 2000); + }).catch(() => { + els.footerStatus.textContent = 'COPY FAILED'; + }); + }); + } + + // ======================================== + // CLOCK UPDATES + // ======================================== + function updateClock() { + const now = new Date(); + const dateStr = now.toISOString().slice(0, 19).replace('T', ' '); + + if (els.loadingDatetime) { + els.loadingDatetime.textContent = dateStr; + } + if (els.headerTime) { + els.headerTime.textContent = dateStr; + } + } + + // ======================================== + // INITIALIZATION + // ======================================== + function init() { + cacheElements(); + + // Title scramble effect + const scrambler = new TextScramble(els.loadingTitle); + setTimeout(() => { + scrambler.setText('CANNIBAL GIRLS'); + }, 500); + + // Clock + updateClock(); + setInterval(updateClock, 1000); + + // Bind events + bindEvents(); + + console.log('%c CANNIBAL GIRLS // ARCHIVE TERMINAL v2.4.1 ', 'background: #0F0D0D; color: #E85D75; font-family: monospace; padding: 4px 8px;'); + console.log('%c SYSTEM ONLINE ', 'background: #181414; color: #4A7C59; font-family: monospace; padding: 2px 6px;'); + } + + // Start when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } +})(); diff --git a/web_files/resources.js b/web_files/resources.js new file mode 100644 index 0000000..473a0f4 diff --git a/web_files/style.css b/web_files/style.css new file mode 100644 index 0000000..28a89de --- /dev/null +++ b/web_files/style.css @@ -0,0 +1,1386 @@ +/* ======================================== + CANNIBAL GIRLS - ARCHIVE TERMINAL + Art Direction & Implementation + ======================================== */ + +/* ---- 1. COLOR PALETTE ---- + Primary: #E85D75 - Reddish Pink (accents, highlights) + Dark Pink: #B8304E - Deep crimson (hover states, active) + Background: #0F0D0D - Warm near-black + Surface: #181414 - Panel backgrounds + Surface Lit: #1E1A1A - Elevated panels + Border: #3A2A2A - Dark brown-red borders + Border Light: #4A3535 - Subtle borders + Text Primary: #E8D5D5 - Warm off-white + Text Sec: #8B6F6F - Muted pink-gray + Text Dim: #5A4545 - Very muted + Accent Green: #4A7C59 - Terminal cursor, sparing use + Accent Rust: #8B4513 - Warnings, sparing use + Shadow: rgba(0, 0, 0, 0.9) + Glow: rgba(232, 93, 117, 0.2) +*/ + +/* ---- 2. FONTS ---- + Display: 'VT323', monospace - Large headers, pixelated terminal feel + Primary: 'Courier Prime', monospace - Body text, clean but vintage + UI: 'Space Mono', monospace - UI labels, structured data + Fallback: 'Courier New', Courier, monospace +*/ + +/* ---- 3. BASE RESET ---- */ +*, *::before, *::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +::selection { + background: #E85D75; + color: #0F0D0D; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #0F0D0D; + border-left: 1px solid #3A2A2A; +} + +::-webkit-scrollbar-thumb { + background: #3A2A2A; + border: 1px solid #4A3535; +} + +::-webkit-scrollbar-thumb:hover { + background: #4A3535; +} + +html { + font-size: 16px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: 'Courier Prime', 'Courier New', Courier, monospace; + background: #0F0D0D; + color: #E8D5D5; + overflow: hidden; + height: 100vh; + width: 100vw; + line-height: 1.4; +} + +/* ---- 4. OVERLAY EFFECTS ---- */ + +/* Scanlines - subtle horizontal lines */ +.scanlines { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9998; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 0, 0, 0.03) 2px, + rgba(0, 0, 0, 0.03) 4px + ); + animation: scanlineMove 8s linear infinite; +} + +@keyframes scanlineMove { + 0% { background-position: 0 0; } + 100% { background-position: 0 100%; } +} + +/* Noise texture overlay */ +.noise-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9997; + opacity: 0.04; + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); +} + +/* Vignette - dark edges */ +.vignette { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + z-index: 9996; + background: radial-gradient( + ellipse at center, + transparent 0%, + transparent 50%, + rgba(15, 13, 13, 0.4) 100% + ); +} + +/* ---- 5. LOADING SCREEN ---- */ +.loading-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0F0D0D; + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + transition: opacity 0.8s ease-out, visibility 0.8s; +} + +.loading-screen.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.loading-content { + width: 100%; + max-width: 800px; + height: 100vh; + display: flex; + flex-direction: column; + padding: 2rem; + position: relative; +} + +.loading-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 1rem; + border-bottom: 1px solid #3A2A2A; + font-family: 'Space Mono', monospace; + font-size: 0.75rem; + letter-spacing: 0.1em; + color: #5A4545; +} + +.status-line { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.status-indicator { + display: inline-block; + width: 8px; + height: 8px; + background: #4A7C59; + animation: blink 2s ease-in-out infinite; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.3; } +} + +.loading-center { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1.5rem; +} + +.loading-title { + font-family: 'VT323', monospace; + font-size: clamp(3rem, 10vw, 6rem); + font-weight: 400; + color: #E85D75; + text-transform: uppercase; + letter-spacing: 0.15em; + text-shadow: + 0 0 10px rgba(232, 93, 117, 0.3), + 0 0 40px rgba(232, 93, 117, 0.1); + line-height: 1; + animation: titleFlicker 4s ease-in-out infinite; +} + +@keyframes titleFlicker { + 0%, 90%, 100% { opacity: 1; } + 92% { opacity: 0.8; } + 94% { opacity: 0.95; } + 96% { opacity: 0.7; } +} + +.loading-separator { + display: flex; + align-items: center; + gap: 1rem; + width: 100%; + max-width: 400px; +} + +.sep-line { + flex: 1; + height: 1px; + background: #3A2A2A; +} + +.sep-diamond { + color: #B8304E; + font-size: 0.6rem; + animation: diamondPulse 2s ease-in-out infinite; +} + +@keyframes diamondPulse { + 0%, 100% { opacity: 0.5; transform: scale(1); } + 50% { opacity: 1; transform: scale(1.2); } +} + +.loading-subtitle { + font-family: 'Space Mono', monospace; + font-size: 0.85rem; + color: #5A4545; + letter-spacing: 0.3em; + text-transform: uppercase; +} + +.load-btn { + margin-top: 2rem; + background: transparent; + border: 1px solid #E85D75; + color: #E85D75; + padding: 1rem 2.5rem; + font-family: 'VT323', monospace; + font-size: 1.5rem; + letter-spacing: 0.2em; + cursor: pointer; + position: relative; + transition: all 0.2s ease; + text-transform: uppercase; +} + +.load-btn::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(232, 93, 117, 0.05); + opacity: 0; + transition: opacity 0.2s ease; +} + +.load-btn:hover::before { + opacity: 1; +} + +.load-btn:hover { + border-color: #B8304E; + color: #B8304E; + box-shadow: + 0 0 15px rgba(232, 93, 117, 0.2), + inset 0 0 15px rgba(232, 93, 117, 0.05); + text-shadow: 0 0 8px rgba(232, 93, 117, 0.3); +} + +.load-btn:active { + transform: scale(0.98); +} + +.btn-bracket { + color: #5A4545; + transition: color 0.2s; +} + +.load-btn:hover .btn-bracket { + color: #8B6F6F; +} + +.loading-footer { + padding-top: 1rem; + border-top: 1px solid #3A2A2A; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; + color: #5A4545; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.footer-top, .footer-bottom { + display: flex; + gap: 0.5rem; + align-items: baseline; +} + +.footer-label { + color: #8B6F6F; + min-width: 80px; +} + +.footer-value { + color: #E8D5D5; +} + +.session-id { + font-size: 0.65rem; + color: #8B6F6F; + word-break: break-all; + letter-spacing: 0.05em; +} + +/* ---- 6. LOADING PHASE ---- */ +.loading-phase { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0F0D0D; + display: flex; + align-items: center; + justify-content: center; + z-index: 1001; + transition: opacity 0.5s ease-out, visibility 0.5s; +} + +.loading-phase.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.phase-content { + width: 100%; + max-width: 700px; + padding: 2rem; +} + +.phase-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 2rem; + padding-bottom: 1rem; + border-bottom: 1px solid #3A2A2A; + font-family: 'Space Mono', monospace; + font-size: 0.8rem; + color: #E85D75; + letter-spacing: 0.15em; +} + +.phase-icon { + animation: blink 1s ease-in-out infinite; +} + +.phase-terminal { + background: #181414; + border: 1px solid #3A2A2A; + padding: 1.5rem; + margin-bottom: 2rem; + min-height: 200px; + font-family: 'Courier Prime', monospace; + font-size: 0.85rem; + line-height: 1.6; + color: #8B6F6F; + position: relative; + overflow: hidden; +} + +.phase-terminal::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 2px; + background: #E85D75; + opacity: 0.3; +} + +.terminal-output { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.terminal-line { + display: flex; + gap: 0.5rem; +} + +.terminal-line .line-prompt { + color: #4A7C59; + white-space: nowrap; +} + +.terminal-line .line-content { + color: #E8D5D5; +} + +.terminal-line .line-content.success { + color: #4A7C59; +} + +.terminal-line .line-content.warning { + color: #8B4513; +} + +.terminal-line .line-content.error { + color: #B8304E; +} + +.terminal-cursor { + display: inline-block; + color: #4A7C59; + animation: cursorBlink 0.7s step-end infinite; + margin-top: 0.5rem; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +.phase-progress { + border: 1px solid #3A2A2A; + padding: 1.5rem; + background: #181414; +} + +.progress-label { + display: flex; + justify-content: space-between; + margin-bottom: 0.75rem; + font-family: 'Space Mono', monospace; + font-size: 0.75rem; + letter-spacing: 0.1em; + color: #8B6F6F; +} + +.progress-bar { + width: 100%; + height: 4px; + background: #1E1A1A; + border: 1px solid #3A2A2A; + position: relative; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: #E85D75; + width: 0%; + transition: width 0.3s ease-out; + box-shadow: 0 0 10px rgba(232, 93, 117, 0.3); +} + +/* ---- 7. ARCHIVE BROWSER ---- */ +.archive-browser { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0F0D0D; + display: flex; + flex-direction: column; + z-index: 100; + opacity: 0; + visibility: hidden; + transition: opacity 0.5s ease-out, visibility 0.5s; +} + +.archive-browser.active { + opacity: 1; + visibility: visible; +} + +/* Header */ +.archive-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1.5rem; + border-bottom: 1px solid #3A2A2A; + background: #181414; + min-height: 50px; +} + +.header-left { + display: flex; + align-items: baseline; + gap: 0.75rem; +} + +.archive-title { + font-family: 'VT323', monospace; + font-size: 1.5rem; + font-weight: 400; + color: #E85D75; + letter-spacing: 0.1em; + text-shadow: 0 0 10px rgba(232, 93, 117, 0.2); +} + +.header-separator { + color: #3A2A2A; + font-family: 'Space Mono', monospace; +} + +.header-subtitle { + font-family: 'Space Mono', monospace; + font-size: 0.75rem; + color: #5A4545; + letter-spacing: 0.2em; +} + +.header-right { + display: flex; + align-items: center; + gap: 2rem; +} + +.header-stats { + display: flex; + gap: 1.5rem; +} + +.stat-item { + display: flex; + gap: 0.5rem; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; + letter-spacing: 0.1em; +} + +.stat-label { + color: #5A4545; +} + +.stat-value { + color: #E8D5D5; +} + +.header-time { + font-family: 'Space Mono', monospace; + font-size: 0.7rem; + color: #8B6F6F; + letter-spacing: 0.1em; +} + +/* Body Layout */ +.archive-body { + flex: 1; + display: grid; + grid-template-columns: 220px 1fr 320px; + overflow: hidden; +} + +/* Sidebar */ +.archive-sidebar { + background: #181414; + border-right: 1px solid #3A2A2A; + display: flex; + flex-direction: column; + overflow-y: auto; +} + +.sidebar-section { + padding: 1.25rem; + border-bottom: 1px solid #3A2A2A; +} + +.sidebar-title { + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + letter-spacing: 0.2em; + margin-bottom: 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid #2B1D1A; +} + +.sidebar-menu { + list-style: none; + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.sidebar-item { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.6rem 0.75rem; + cursor: pointer; + transition: all 0.15s ease; + font-family: 'Courier Prime', monospace; + font-size: 0.8rem; + border: 1px solid transparent; +} + +.sidebar-item:hover { + background: rgba(232, 93, 117, 0.05); + border-color: #3A2A2A; +} + +.sidebar-item.active { + background: rgba(232, 93, 117, 0.08); + border-color: #E85D75; + color: #E85D75; +} + +.sidebar-item.active .item-icon { + color: #E85D75; +} + +.item-icon { + color: #5A4545; + font-size: 0.7rem; + width: 16px; + text-align: center; +} + +.item-label { + flex: 1; + letter-spacing: 0.05em; +} + +.item-count { + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + background: #1E1A1A; + padding: 0.1rem 0.4rem; + border: 1px solid #3A2A2A; +} + +.system-info { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.sys-row { + display: flex; + justify-content: space-between; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; +} + +.sys-label { + color: #5A4545; +} + +.sys-value { + color: #8B6F6F; +} + +.sys-value.status-ok { + color: #4A7C59; +} + +/* Content Area */ +.archive-content { + display: flex; + flex-direction: column; + overflow: hidden; + background: #0F0D0D; +} + +/* Toolbar */ +.content-toolbar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1.25rem; + border-bottom: 1px solid #3A2A2A; + background: #181414; + gap: 1rem; +} + +.toolbar-left { + display: flex; + align-items: center; + gap: 1.5rem; +} + +.view-switcher { + display: flex; + gap: 0.25rem; +} + +.view-btn { + display: flex; + align-items: center; + gap: 0.4rem; + padding: 0.4rem 0.75rem; + background: transparent; + border: 1px solid #3A2A2A; + color: #5A4545; + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + letter-spacing: 0.1em; + cursor: pointer; + transition: all 0.15s ease; +} + +.view-btn:hover { + border-color: #4A3535; + color: #8B6F6F; +} + +.view-btn.active { + border-color: #E85D75; + color: #E85D75; + background: rgba(232, 93, 117, 0.05); +} + +.view-icon { + font-size: 0.8rem; +} + +.sort-controls { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.sort-label { + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + letter-spacing: 0.1em; +} + +.sort-select { + background: #181414; + border: 1px solid #3A2A2A; + color: #8B6F6F; + font-family: 'Courier Prime', monospace; + font-size: 0.75rem; + padding: 0.35rem 0.5rem; + cursor: pointer; + outline: none; +} + +.sort-select:focus { + border-color: #E85D75; +} + +.sort-select option { + background: #181414; + color: #E8D5D5; +} + +.toolbar-right { + display: flex; + align-items: center; +} + +.search-box { + display: flex; + align-items: center; + gap: 0.5rem; + background: #181414; + border: 1px solid #3A2A2A; + padding: 0.35rem 0.75rem; + transition: border-color 0.15s; +} + +.search-box:focus-within { + border-color: #E85D75; +} + +.search-icon { + color: #5A4545; + font-size: 0.9rem; +} + +.search-input { + background: transparent; + border: none; + color: #E8D5D5; + font-family: 'Courier Prime', monospace; + font-size: 0.8rem; + outline: none; + width: 200px; +} + +.search-input::placeholder { + color: #5A4545; +} + +/* File List Container */ +.file-list-container { + flex: 1; + overflow-y: auto; + padding: 0.5rem 0; +} + +.file-list-header { + display: grid; + grid-template-columns: 1fr 100px 100px 140px; + gap: 0.5rem; + padding: 0.5rem 1.25rem; + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + letter-spacing: 0.15em; + border-bottom: 1px solid #2B1D1A; + position: sticky; + top: 0; + background: #0F0D0D; + z-index: 10; +} + +.file-list { + display: flex; + flex-direction: column; +} + +/* File Item - List View */ +.file-item { + display: grid; + grid-template-columns: 1fr 100px 100px 140px; + gap: 0.5rem; + padding: 0.65rem 1.25rem; + align-items: center; + cursor: pointer; + border-bottom: 1px solid #1E1A1A; + transition: all 0.15s ease; + font-family: 'Courier Prime', monospace; + font-size: 0.8rem; + position: relative; +} + +.file-item::before { + content: ''; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 2px; + background: transparent; + transition: background 0.15s; +} + +.file-item:hover { + background: rgba(232, 93, 117, 0.03); +} + +.file-item:hover::before { + background: #E85D75; +} + +.file-item.selected { + background: rgba(232, 93, 117, 0.06); + border-bottom-color: #3A2A2A; +} + +.file-item.selected::before { + background: #E85D75; +} + +.file-name-cell { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.file-icon { + color: #5A4545; + font-size: 0.9rem; + width: 20px; + text-align: center; +} + +.file-name { + color: #E8D5D5; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.file-type { + color: #8B6F6F; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; + text-transform: uppercase; +} + +.file-size { + color: #8B6F6F; + font-family: 'Space Mono', monospace; + font-size: 0.75rem; +} + +.file-date { + color: #5A4545; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; +} + +/* File Item - Grid View */ +.file-list.grid-view { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + gap: 1px; + padding: 0.5rem; +} + +.file-list.grid-view .file-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 1.5rem 1rem; + gap: 0.5rem; + text-align: center; + aspect-ratio: 1; + border: 1px solid #1E1A1A; +} + +.file-list.grid-view .file-item::before { + display: none; +} + +.file-list.grid-view .file-name-cell { + flex-direction: column; + gap: 0.5rem; +} + +.file-list.grid-view .file-icon { + font-size: 1.5rem; + color: #8B6F6F; +} + +.file-list.grid-view .file-name { + font-size: 0.75rem; + max-width: 120px; +} + +.file-list.grid-view .file-type, +.file-list.grid-view .file-size, +.file-list.grid-view .file-date { + font-size: 0.65rem; +} + +/* Preview Pane */ +.archive-preview { + background: #181414; + border-left: 1px solid #3A2A2A; + display: flex; + flex-direction: column; + overflow-y: auto; + transition: transform 0.3s ease; +} + +.archive-preview.closed { + transform: translateX(100%); + width: 0; + border: none; +} + +.preview-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + border-bottom: 1px solid #3A2A2A; +} + +.preview-title { + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + letter-spacing: 0.2em; +} + +.preview-close { + background: transparent; + border: 1px solid #3A2A2A; + color: #5A4545; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 0.7rem; + transition: all 0.15s; +} + +.preview-close:hover { + border-color: #B8304E; + color: #B8304E; +} + +.preview-content { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem 1rem; + border-bottom: 1px solid #3A2A2A; +} + +.preview-placeholder { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + color: #5A4545; +} + +.placeholder-icon { + font-size: 2rem; + opacity: 0.3; +} + +.placeholder-text { + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + letter-spacing: 0.15em; + text-align: center; +} + +.preview-file-icon { + font-size: 4rem; + color: #E85D75; + opacity: 0.8; + text-shadow: 0 0 20px rgba(232, 93, 117, 0.2); +} + +.preview-meta { + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + border-bottom: 1px solid #3A2A2A; +} + +.meta-row { + display: flex; + justify-content: space-between; + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + gap: 0.5rem; +} + +.meta-label { + color: #5A4545; + white-space: nowrap; +} + +.meta-value { + color: #8B6F6F; + text-align: right; + word-break: break-all; +} + +.meta-value.checksum { + font-size: 0.55rem; + color: #5A4545; +} + +.preview-actions { + padding: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.action-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.6rem; + background: transparent; + border: 1px solid #3A2A2A; + color: #8B6F6F; + font-family: 'Space Mono', monospace; + font-size: 0.7rem; + letter-spacing: 0.1em; + cursor: pointer; + transition: all 0.15s; +} + +.action-btn:hover { + border-color: #E85D75; + color: #E85D75; + background: rgba(232, 93, 117, 0.05); +} + +.action-icon { + font-size: 0.8rem; +} + +/* Footer */ +.archive-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 1.5rem; + border-top: 1px solid #3A2A2A; + background: #181414; + font-family: 'Space Mono', monospace; + font-size: 0.65rem; + color: #5A4545; + letter-spacing: 0.1em; + min-height: 36px; +} + +.footer-item { + white-space: nowrap; +} + +.footer-center { + color: #4A7C59; +} + +.session-truncated { + color: #5A4545; +} + +/* ---- 8. GLITCH EFFECTS ---- */ + +.glitch-text { + position: relative; +} + +.glitch-text::before, +.glitch-text::after { + content: attr(data-text); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.glitch-text::before { + left: 2px; + text-shadow: -2px 0 #B8304E; + clip: rect(24px, 550px, 90px, 0); + animation: glitch-anim-1 2.5s infinite linear alternate-reverse; +} + +.glitch-text::after { + left: -2px; + text-shadow: -2px 0 #4A7C59; + clip: rect(85px, 550px, 140px, 0); + animation: glitch-anim-2 3s infinite linear alternate-reverse; +} + +@keyframes glitch-anim-1 { + 0% { clip: rect(20px, 9999px, 15px, 0); } + 20% { clip: rect(60px, 9999px, 70px, 0); } + 40% { clip: rect(20px, 9999px, 5px, 0); } + 60% { clip: rect(80px, 9999px, 50px, 0); } + 80% { clip: rect(10px, 9999px, 40px, 0); } + 100% { clip: rect(50px, 9999px, 20px, 0); } +} + +@keyframes glitch-anim-2 { + 0% { clip: rect(25px, 9999px, 90px, 0); } + 20% { clip: rect(10px, 9999px, 30px, 0); } + 40% { clip: rect(90px, 9999px, 100px, 0); } + 60% { clip: rect(15px, 9999px, 5px, 0); } + 80% { clip: rect(70px, 9999px, 60px, 0); } + 100% { clip: rect(40px, 9999px, 80px, 0); } +} + +/* ---- 9. PANEL REVEAL ANIMATIONS ---- */ + +@keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slideInRight { + from { + opacity: 0; + transform: translateX(20px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes panelReveal { + from { + opacity: 0; + transform: scaleX(0); + transform-origin: left; + } + to { + opacity: 1; + transform: scaleX(1); + transform-origin: left; + } +} + +.animate-slide-left { + animation: slideInLeft 0.4s ease-out forwards; +} + +.animate-slide-right { + animation: slideInRight 0.4s ease-out forwards; +} + +.animate-fade-up { + animation: fadeInUp 0.3s ease-out forwards; +} + +/* Staggered animations */ +.stagger-1 { animation-delay: 0.05s; } +.stagger-2 { animation-delay: 0.1s; } +.stagger-3 { animation-delay: 0.15s; } +.stagger-4 { animation-delay: 0.2s; } +.stagger-5 { animation-delay: 0.25s; } + +/* ---- 10. RESPONSIVE ---- */ +@media (max-width: 1024px) { + .archive-body { + grid-template-columns: 180px 1fr 280px; + } +} + +@media (max-width: 768px) { + .archive-body { + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + } + + .archive-sidebar { + display: none; + } + + .archive-preview { + position: fixed; + right: 0; + top: 50px; + bottom: 36px; + width: 100%; + max-width: 400px; + z-index: 200; + transform: translateX(100%); + } + + .archive-preview.active { + transform: translateX(0); + } + + .file-list-header { + grid-template-columns: 1fr 80px 80px; + } + + .file-item { + grid-template-columns: 1fr 80px 80px; + } + + .col-date { + display: none; + } +} + +/* ---- 11. UTILITY ---- */ +.hidden { + display: none !important; +} + +.text-pink { color: #E85D75; } +.text-dim { color: #5A4545; } +.text-muted { color: #8B6F6F; } + +/* Focus states */ +button:focus-visible, +select:focus-visible, +input:focus-visible { + outline: 1px solid #E85D75; + outline-offset: 1px; +} + +/* Hover glow for interactive elements */ +.interactive-glow:hover { + box-shadow: 0 0 8px rgba(232, 93, 117, 0.15); +} + +/* Subtle border glow animation */ +.border-glow { + animation: borderGlow 3s ease-in-out infinite; +} + +@keyframes borderGlow { + 0%, 100% { border-color: #3A2A2A; } + 50% { border-color: #4A3535; } +} + +/* Terminal text effect */ +.typing-effect { + overflow: hidden; + white-space: nowrap; + animation: typing 2s steps(40, end); +} + +@keyframes typing { + from { width: 0; } + to { width: 100%; } +} + +/* Subtle shake for errors */ +.shake { + animation: shake 0.3s ease-in-out; +} + +@keyframes shake { + 0%, 100% { transform: translateX(0); } + 25% { transform: translateX(-3px); } + 75% { transform: translateX(3px); } +} + +/* Loading spinner for small elements */ +.spinner { + display: inline-block; + width: 12px; + height: 12px; + border: 1px solid #3A2A2A; + border-top-color: #E85D75; + animation: spin 0.8s linear infinite; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} diff --git a/webhandler.py b/webhandler.py new file mode 100644 index 0000000..473a0f4 diff --git a/webloader.py b/webloader.py new file mode 100644 index 0000000..473a0f4