/** * 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 = `