Initial commit
This commit is contained in:
0
web_files/files.js
Normal file
0
web_files/files.js
Normal file
286
web_files/index.html
Normal file
286
web_files/index.html
Normal file
@@ -0,0 +1,286 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CANNIBAL GIRLS // ARCHIVE</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=VT323&family=Space+Mono:ital,wght@0,400;0,700;1,400&family=Courier+Prime:wght@400;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- CRT SCANLINE OVERLAY -->
|
||||
<div class="scanlines"></div>
|
||||
|
||||
<!-- NOISE TEXTURE OVERLAY -->
|
||||
<div class="noise-overlay"></div>
|
||||
|
||||
<!-- VIGNETTE -->
|
||||
<div class="vignette"></div>
|
||||
|
||||
<!-- LOADING SCREEN -->
|
||||
<section id="loading-screen" class="loading-screen">
|
||||
<div class="loading-content">
|
||||
<div class="loading-header">
|
||||
<div class="status-line">
|
||||
<span class="status-indicator"></span>
|
||||
<span class="status-text">SYSTEM ONLINE</span>
|
||||
</div>
|
||||
<div class="datetime" id="loading-datetime"></div>
|
||||
</div>
|
||||
|
||||
<div class="loading-center">
|
||||
<h1 class="loading-title" id="loading-title">CANNIBAL GIRLS</h1>
|
||||
<div class="loading-separator">
|
||||
<span class="sep-line"></span>
|
||||
<span class="sep-diamond">▼</span>
|
||||
<span class="sep-line"></span>
|
||||
</div>
|
||||
<p class="loading-subtitle" id="loading-subtitle">ARCHIVE TERMINAL v2.4.1</p>
|
||||
|
||||
<button id="load-files-btn" class="load-btn">
|
||||
<span class="btn-bracket">[</span>
|
||||
<span class="btn-text">LOAD FILES</span>
|
||||
<span class="btn-bracket">]</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="loading-footer">
|
||||
<div class="footer-top">
|
||||
<span class="footer-label">PRODUCER:</span>
|
||||
<span class="footer-value">t.me/forgecadrape</span>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<span class="footer-label">SESSION:</span>
|
||||
<span class="footer-value session-id">056834db96cedde6012da9d5402c683c0eb260ed866da145a8f86e7f329bf77222</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- LOADING PHASE OVERLAY -->
|
||||
<section id="loading-phase" class="loading-phase hidden">
|
||||
<div class="phase-content">
|
||||
<div class="phase-header">
|
||||
<span class="phase-icon">▶</span>
|
||||
<span class="phase-title">INITIALIZING ARCHIVE PROTOCOL</span>
|
||||
</div>
|
||||
<div class="phase-terminal">
|
||||
<div class="terminal-output" id="terminal-output"></div>
|
||||
<div class="terminal-cursor">_</div>
|
||||
</div>
|
||||
<div class="phase-progress">
|
||||
<div class="progress-label">
|
||||
<span id="progress-text">MOUNTING VOLUMES...</span>
|
||||
<span id="progress-percent">0%</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ARCHIVE BROWSER -->
|
||||
<main id="archive-browser" class="archive-browser hidden">
|
||||
<!-- TOP BAR -->
|
||||
<header class="archive-header">
|
||||
<div class="header-left">
|
||||
<h1 class="archive-title">CANNIBAL_GIRLS</h1>
|
||||
<span class="header-separator">//</span>
|
||||
<span class="header-subtitle">ARCHIVE_BROWSER</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="header-stats">
|
||||
<span class="stat-item">
|
||||
<span class="stat-label">FILES:</span>
|
||||
<span class="stat-value" id="file-count">0</span>
|
||||
</span>
|
||||
<span class="stat-item">
|
||||
<span class="stat-label">SIZE:</span>
|
||||
<span class="stat-value" id="total-size">0 GB</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="header-time" id="header-time"></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- MAIN CONTENT AREA -->
|
||||
<div class="archive-body">
|
||||
<!-- SIDEBAR -->
|
||||
<aside class="archive-sidebar">
|
||||
<div class="sidebar-section">
|
||||
<h3 class="sidebar-title">CATALOG</h3>
|
||||
<ul class="sidebar-menu" id="sidebar-menu">
|
||||
<li class="sidebar-item active" data-filter="all">
|
||||
<span class="item-icon">■</span>
|
||||
<span class="item-label">ALL_FILES</span>
|
||||
<span class="item-count" id="count-all">0</span>
|
||||
</li>
|
||||
<li class="sidebar-item" data-filter="video">
|
||||
<span class="item-icon">▶</span>
|
||||
<span class="item-label">VIDEO</span>
|
||||
<span class="item-count" id="count-video">0</span>
|
||||
</li>
|
||||
<li class="sidebar-item" data-filter="image">
|
||||
<span class="item-icon">■</span>
|
||||
<span class="item-label">IMAGES</span>
|
||||
<span class="item-count" id="count-image">0</span>
|
||||
</li>
|
||||
<li class="sidebar-item" data-filter="audio">
|
||||
<span class="item-icon">♬</span>
|
||||
<span class="item-label">AUDIO</span>
|
||||
<span class="item-count" id="count-audio">0</span>
|
||||
</li>
|
||||
<li class="sidebar-item" data-filter="document">
|
||||
<span class="item-icon">☰</span>
|
||||
<span class="item-label">DOCS</span>
|
||||
<span class="item-count" id="count-document">0</span>
|
||||
</li>
|
||||
<li class="sidebar-item" data-filter="archive">
|
||||
<span class="item-icon">■</span>
|
||||
<span class="item-label">ARCHIVES</span>
|
||||
<span class="item-count" id="count-archive">0</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-section">
|
||||
<h3 class="sidebar-title">SYSTEM</h3>
|
||||
<div class="system-info">
|
||||
<div class="sys-row">
|
||||
<span class="sys-label">STATUS:</span>
|
||||
<span class="sys-value status-ok">ONLINE</span>
|
||||
</div>
|
||||
<div class="sys-row">
|
||||
<span class="sys-label">UPTIME:</span>
|
||||
<span class="sys-value" id="uptime">00:00:00</span>
|
||||
</div>
|
||||
<div class="sys-row">
|
||||
<span class="sys-label">VERSION:</span>
|
||||
<span class="sys-value">2.4.1-b</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- FILE LIST AREA -->
|
||||
<div class="archive-content">
|
||||
<!-- TOOLBAR -->
|
||||
<div class="content-toolbar">
|
||||
<div class="toolbar-left">
|
||||
<div class="view-switcher">
|
||||
<button class="view-btn active" data-view="list" title="List View">
|
||||
<span class="view-icon">☰</span>
|
||||
<span class="view-label">LIST</span>
|
||||
</button>
|
||||
<button class="view-btn" data-view="grid" title="Grid View">
|
||||
<span class="view-icon">■■</span>
|
||||
<span class="view-label">GRID</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="sort-controls">
|
||||
<span class="sort-label">SORT:</span>
|
||||
<select id="sort-select" class="sort-select">
|
||||
<option value="name-asc">NAME [A-Z]</option>
|
||||
<option value="name-desc">NAME [Z-A]</option>
|
||||
<option value="date-desc">DATE [NEWEST]</option>
|
||||
<option value="date-asc">DATE [OLDEST]</option>
|
||||
<option value="size-desc">SIZE [LARGEST]</option>
|
||||
<option value="size-asc">SIZE [SMALLEST]</option>
|
||||
<option value="type">TYPE</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<div class="search-box">
|
||||
<span class="search-icon">⚲</span>
|
||||
<input type="text" id="search-input" class="search-input" placeholder="SEARCH FILES...">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FILE LIST -->
|
||||
<div class="file-list-container">
|
||||
<div class="file-list-header" id="file-list-header">
|
||||
<span class="col-name">NAME</span>
|
||||
<span class="col-type">TYPE</span>
|
||||
<span class="col-size">SIZE</span>
|
||||
<span class="col-date">DATE</span>
|
||||
</div>
|
||||
<div class="file-list" id="file-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PREVIEW PANE -->
|
||||
<aside class="archive-preview" id="preview-pane">
|
||||
<div class="preview-header">
|
||||
<span class="preview-title">PREVIEW</span>
|
||||
<button class="preview-close" id="preview-close">✕</button>
|
||||
</div>
|
||||
<div class="preview-content" id="preview-content">
|
||||
<div class="preview-placeholder">
|
||||
<span class="placeholder-icon">■</span>
|
||||
<span class="placeholder-text">SELECT A FILE TO PREVIEW</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-meta" id="preview-meta">
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">FILENAME:</span>
|
||||
<span class="meta-value" id="meta-filename">---</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">TYPE:</span>
|
||||
<span class="meta-value" id="meta-type">---</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">SIZE:</span>
|
||||
<span class="meta-value" id="meta-size">---</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">CREATED:</span>
|
||||
<span class="meta-value" id="meta-created">---</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">MODIFIED:</span>
|
||||
<span class="meta-value" id="meta-modified">---</span>
|
||||
</div>
|
||||
<div class="meta-row">
|
||||
<span class="meta-label">CHECKSUM:</span>
|
||||
<span class="meta-value checksum" id="meta-checksum">---</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-actions">
|
||||
<button class="action-btn" id="action-download">
|
||||
<span class="action-icon">▼</span>
|
||||
DOWNLOAD
|
||||
</button>
|
||||
<button class="action-btn" id="action-copy">
|
||||
<span class="action-icon">⚬</span>
|
||||
COPY HASH
|
||||
</button>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<footer class="archive-footer">
|
||||
<div class="footer-left">
|
||||
<span class="footer-item">PROD: t.me/forgecadrape</span>
|
||||
</div>
|
||||
<div class="footer-center">
|
||||
<span class="footer-item" id="footer-status">READY</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span class="footer-item session-truncated">SESSION: 056834db96ce...f77222</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<!-- AUDIO ELEMENT -->
|
||||
<audio id="bg-audio" preload="auto"></audio>
|
||||
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
666
web_files/index.js
Normal file
666
web_files/index.js
Normal file
@@ -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 += `<span class="text-dim">${char}</span>`;
|
||||
} 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 = '<span class="spinner"></span>';
|
||||
|
||||
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 = '<span style="color: #5A4545; grid-column: 1 / -1; text-align: center; padding: 2rem;">NO FILES FOUND</span>';
|
||||
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 = `
|
||||
<div class="file-name-cell">
|
||||
<span class="file-icon">${getFileIcon(file.type)}</span>
|
||||
<span class="file-name" title="${file.name}">${file.name}</span>
|
||||
</div>
|
||||
<span class="file-type">${file.type}</span>
|
||||
<span class="file-size">${formatBytes(file.size)}</span>
|
||||
<span class="file-date">${formatDateShort(file.date)}</span>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<div class="preview-placeholder">
|
||||
<span class="preview-file-icon">${getFileIcon(file.type)}</span>
|
||||
<span class="placeholder-text">${file.type.toUpperCase()} FILE</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 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 = `
|
||||
<div class="preview-placeholder">
|
||||
<span class="placeholder-icon">■</span>
|
||||
<span class="placeholder-text">SELECT A FILE TO PREVIEW</span>
|
||||
</div>
|
||||
`;
|
||||
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();
|
||||
}
|
||||
})();
|
||||
0
web_files/resources.js
Normal file
0
web_files/resources.js
Normal file
1386
web_files/style.css
Normal file
1386
web_files/style.css
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user