Initial commit

This commit is contained in:
unknown
2026-05-19 01:41:07 +02:00
commit 033d0ffcb7
19 changed files with 2532 additions and 0 deletions

0
web_files/files.js Normal file
View File

286
web_files/index.html Normal file
View 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">&#9660;</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">&#9654;</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">&#9632;</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">&#9654;</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">&#9632;</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">&#9836;</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">&#9776;</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">&#9632;</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">&#9776;</span>
<span class="view-label">LIST</span>
</button>
<button class="view-btn" data-view="grid" title="Grid View">
<span class="view-icon">&#9632;&#9632;</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">&#9906;</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">&#10005;</button>
</div>
<div class="preview-content" id="preview-content">
<div class="preview-placeholder">
<span class="placeholder-icon">&#9632;</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">&#9660;</span>
DOWNLOAD
</button>
<button class="action-btn" id="action-copy">
<span class="action-icon">&#9900;</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
View 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: '&#9654;',
audio: '&#9836;',
image: '&#9632;',
document: '&#9776;',
archive: '&#9632;'
};
return icons[type] || '&#9632;';
}
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">&#9632;</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
View File

1386
web_files/style.css Normal file

File diff suppressed because it is too large Load Diff