anycoder-33b9d15e / index.html
HI7RAI's picture
Upload folder using huggingface_hub
0d1f33b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CyberDeck | 3D Extension & Asset Manager</title>
<!-- External Libraries -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Share+Tech+Mono&display=swap" rel="stylesheet">
<style>
:root {
--neon-cyan: #00f3ff;
--neon-pink: #ff00ff;
--neon-green: #0aff0a;
--dark-bg: #050505;
--panel-bg: rgba(20, 20, 20, 0.95);
--border-color: #333;
--scan-line: rgba(0, 243, 255, 0.05);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--dark-bg);
color: #e0e0e0;
font-family: 'Share Tech Mono', monospace;
overflow-x: hidden;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* Background Effects */
.bg-grid {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(0, 243, 255, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 243, 255, 0.1) 1px, transparent 1px);
background-size: 40px 40px;
z-index: -2;
opacity: 0.15;
animation: gridMove 20s linear infinite;
}
@keyframes gridMove {
0% { transform: translate(0, 0); }
100% { transform: translate(40px, 40px); }
}
.vignette {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, transparent 50%, black 100%);
z-index: -1;
pointer-events: none;
}
/* Header */
header {
padding: 20px 40px;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
background: rgba(5, 5, 5, 0.85);
backdrop-filter: blur(10px);
z-index: 100;
position: sticky;
top: 0;
box-shadow: 0 0 20px rgba(0, 243, 255, 0.1);
}
.logo {
font-family: 'Orbitron', sans-serif;
font-size: 1.5rem;
color: var(--neon-cyan);
text-shadow: 0 0 10px var(--neon-cyan);
letter-spacing: 2px;
display: flex;
align-items: center;
gap: 12px;
}
.anycoder-link {
color: var(--neon-green);
text-decoration: none;
font-size: 0.85rem;
border: 1px solid var(--neon-green);
padding: 6px 16px;
border-radius: 4px;
transition: all 0.3s ease;
font-weight: bold;
}
.anycoder-link:hover {
background: var(--neon-green);
color: black;
box-shadow: 0 0 15px var(--neon-green);
}
/* Navigation Tabs */
.nav-tabs {
display: flex;
background: rgba(0, 0, 0, 0.6);
border-bottom: 1px solid var(--border-color);
padding: 0 40px;
overflow-x: auto;
}
.nav-tab {
padding: 15px 30px;
background: transparent;
border: none;
color: #888;
font-family: 'Orbitron', sans-serif;
cursor: pointer;
transition: all 0.3s;
position: relative;
text-transform: uppercase;
font-size: 0.9rem;
white-space: nowrap;
}
.nav-tab.active {
color: var(--neon-cyan);
background: rgba(0, 243, 255, 0.05);
}
.nav-tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: var(--neon-cyan);
box-shadow: 0 0 10px var(--neon-cyan);
}
.nav-tab:hover {
color: #fff;
background: rgba(255, 255, 255, 0.05);
}
/* Main Content */
main {
flex: 1;
padding: 30px;
max-width: 1400px;
margin: 0 auto;
width: 100%;
}
.tab-content {
display: none;
animation: fadeIn 0.5s ease;
}
.tab-content.active {
display: block;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Panels */
.panel {
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 10px;
padding: 25px;
margin-bottom: 25px;
backdrop-filter: blur(5px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
transition: border-color 0.3s;
}
.panel:hover {
border-color: rgba(255, 255, 255, 0.2);
}
.panel-title {
font-family: 'Orbitron', sans-serif;
color: var(--neon-pink);
margin-bottom: 20px;
font-size: 1.3rem;
display: flex;
align-items: center;
gap: 12px;
text-transform: uppercase;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 10px;
}
/* Form Elements */
.input-group {
margin-bottom: 20px;
}
label {
display: block;
font-size: 0.9rem;
color: #aaa;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 1px;
}
input[type="text"],
input[type="file"],
textarea,
select {
width: 100%;
background: rgba(0, 0, 0, 0.6);
border: 1px solid #444;
color: var(--neon-cyan);
padding: 12px;
font-family: 'Share Tech Mono', monospace;
border-radius: 5px;
outline: none;
transition: all 0.3s;
}
input[type="text"]:focus,
textarea:focus,
select:focus {
border-color: var(--neon-cyan);
box-shadow: 0 0 15px rgba(0, 243, 255, 0.15);
}
textarea {
min-height: 120px;
resize: vertical;
font-size: 0.9rem;
}
/* Buttons */
.btn {
background: transparent;
border: 1px solid var(--neon-cyan);
color: var(--neon-cyan);
padding: 10px 20px;
cursor: pointer;
font-family: 'Orbitron', sans-serif;
text-transform: uppercase;
transition: all 0.3s;
position: relative;
overflow: hidden;
border-radius: 5px;
font-size: 0.85rem;
display: inline-flex;
align-items: center;
gap: 8px;
margin-right: 10px;
margin-bottom: 10px;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 243, 255, 0.2), transparent);
transition: 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn:hover {
background: rgba(0, 243, 255, 0.1);
box-shadow: 0 0 15px rgba(0, 243, 255, 0.3);
transform: translateY(-2px);
letter-spacing: 1px;
}
.btn-pink {
border-color: var(--neon-pink);
color: var(--neon-pink);
}
.btn-pink:hover {
background: rgba(255, 0, 255, 0.1);
box-shadow: 0 0 15px rgba(255, 0, 255, 0.3);
}
.btn-green {
border-color: var(--neon-green);
color: var(--neon-green);
}
.btn-green:hover {
background: rgba(10, 255, 10, 0.1);
box-shadow: 0 0 15px rgba(10, 255, 10, 0.3);
}
/* File Tree */
.file-tree {
background: rgba(0, 0, 0, 0.5);
border: 1px solid var(--border-color);
border-radius: 5px;
padding: 15px;
max-height: 300px;
overflow-y: auto;
font-family: 'Share Tech Mono', monospace;
}
.file-tree::-webkit-scrollbar {
width: 8px;
}
.file-tree::-webkit-scrollbar-track {
background: #111;
}
.file-tree::-webkit-scrollbar-thumb {
background: #333;
border-radius: 4px;
}
.file-tree::-webkit-scrollbar-thumb:hover {
background: var(--neon-cyan);
}
.file-item {
padding: 8px 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.2s;
border-radius: 3px;
border-left: 2px solid transparent;
}
.file-item:hover {
background: rgba(0, 243, 255, 0.1);
border-left: 2px solid var(--neon-cyan);
color: #fff;
}
.file-icon {
color: var(--neon-pink);
}
/* Asset List */
.asset-list {
list-style: none;
max-height: 300px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.3);
border-radius: 5px;
padding: 10px;
}
.asset-item {
padding: 10px;
margin-bottom: 8px;
background: rgba(255, 255, 255, 0.03);
border-radius: 5px;
display: flex;
align-items: center;
gap: 10px;
transition: all 0.3s;
border: 1px solid transparent;
}
.asset-item:hover {
background: rgba(0, 243, 255, 0.05);
border-color: rgba(255, 255, 255, 0.1);
}
.asset-checkbox {
width: 18px;
height: 18px;
accent-color: var(--neon-pink);
cursor: pointer;
}
/* 3D Scene */
.scene-3d {
width: 100%;
height: 400px;
background: radial-gradient(circle at center, #1a1a2e, #050505);
display: flex;
align-items: center;
justify-content: center;
perspective: 1000px;
border: 1px solid var(--border-color);
border-radius: 10px;
overflow: hidden;
position: relative;
}
.cube-container {
width: 150px;
height: 150px;
position: relative;
transform-style: preserve-3d;
animation: rotate3d 15s infinite linear;
}
@keyframes rotate3d {
from { transform: rotateX(0deg) rotateY(0deg); }
to { transform: rotateX(360deg) rotateY(360deg); }
}
.cube-face {
position: absolute;
width: 150px;
height: 150px;
background: rgba(0, 243, 255, 0.1);
border: 2px solid var(--neon-cyan);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
font-weight: bold;
color: var(--neon-cyan);
backface-visibility: visible;
text-shadow: 0 0 10px var(--neon-cyan);
box-shadow: 0 0 20px rgba(0, 243, 255, 0.1) inset;
}
.face-front { transform: translateZ(75px); }
.face-back { transform: rotateY(180deg) translateZ(75px); }
.face-right { transform: rotateY(90deg) translateZ(75px); }
.face-left { transform: rotateY(-90deg) translateZ(75px); }
.face-top { transform: rotateX(90deg) translateZ(75px); }
.face-bottom { transform: rotateX(-90deg) translateZ(75px); }
/* Status */
.status-bar {
background: rgba(0, 0, 0, 0.8);
border: 1px solid var(--neon-green);
border-left: 4px solid var(--neon-green);
border-radius: 4px;
padding: 12px 15px;
font-family: 'Share Tech Mono', monospace;
color: var(--neon-green);
margin-top: 20px;
min-height: 45px;
display: flex;
align-items: center;
font-size: 0.9rem;
}
.status-bar i {
margin-right: 10px;
animation: blink 2s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Grid Layout */
.grid-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.grid-options {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin-bottom: 15px;
}
.checkbox-label {
display: flex !important;
align-items: center;
gap: 8px;
cursor: pointer;
color: #ccc !important;
text-transform: none !important;
font-size: 0.9rem;
}
.checkbox-label input {
width: auto !important;
margin: 0;
}
@media (max-width: 768px) {
.grid-2 {
grid-template-columns: 1fr;
}
header {
padding: 15px 20px;
flex-direction: column;
gap: 15px;
text-align: center;
}
.nav-tabs {
padding: 0 10px;
justify-content: flex-start;
}
.nav-tab {
padding: 12px 15px;
font-size: 0.8rem;
}
main {
padding: 15px;
}
.scene-3d {
height: 300px;
}
.cube-container {
width: 120px;
height: 120px;
}
.cube-face {
width: 120px;
height: 120px;
}
.face-front { transform: translateZ(60px); }
.face-back { transform: rotateY(180deg) translateZ(60px); }
.face-right { transform: rotateY(90deg) translateZ(60px); }
.face-left { transform: rotateY(-90deg) translateZ(60px); }
.face-top { transform: rotateX(90deg) translateZ(60px); }
.face-bottom { transform: rotateX(-90deg) translateZ(60px); }
}
</style>
</head>
<body>
<div class="bg-grid"></div>
<div class="vignette"></div>
<header>
<div class="logo">
<i class="fas fa-cube"></i> CYBERDECK // MANAGER
</div>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder <i class="fas fa-external-link-alt"></i>
</a>
</header>
<nav class="nav-tabs">
<button class="nav-tab active" onclick="switchTab('import')">
<i class="fas fa-file-import"></i> Import CRX/ZIP
</button>
<button class="nav-tab" onclick="switchTab('scanner')">
<i class="fas fa-radar"></i> Asset Scanner
</button>
<button class="nav-tab" onclick="switchTab('merger')">
<i class="fas fa-layer-group"></i> File Merger
</button>
<button class="nav-tab" onclick="switchTab('3d')">
<i class="fas fa-cube"></i> 3D Showroom
</button>
</nav>
<main>
<!-- Import CRX/ZIP Tab -->
<div id="import" class="tab-content active">
<div class="panel">
<h2 class="panel-title">
<i class="fas fa-file-archive"></i> Extension Source Manager
</h2>
<div class="grid-2">
<div class="input-group">
<label>Select Extension File (.CRX, .ZIP)</label>
<input type="file" id="crxFile" accept=".crx,.zip" onchange="handleCRXFile(event)">
</div>
<div class="input-group">
<label>File Operations</label>
<button class="btn" onclick="extractFiles()">
<i class="fas fa-unlock"></i> Extract Files
</button>
<button class="btn btn-pink" onclick="repackFiles()">
<i class="fas fa-box"></i> Repack ZIP
</button>
</div>
</div>
<div class="status-bar" id="importStatus">
<i class="fas fa-terminal"></i> Ready for file input...
</div>
</div>
<div class="panel" id="fileTreePanel" style="display: none;">
<h3 class="panel-title">
<i class="fas fa-folder-tree"></i> Extracted Files
</h3>
<div class="file-tree" id="fileTree"></div>
<div class="input-group">
<label>File Content Editor</label>
<textarea id="fileEditor" placeholder="Select a file to view/edit its content..."></textarea>
</div>
</div>
</div>
<!-- Asset Scanner Tab -->
<div id="scanner" class="tab-content">
<div class="panel">
<h2 class="panel-title">
<i class="fas fa-search-location"></i> 3D Asset Scanner
</h2>
<div class="input-group">
<label>Input Source (URLs or HTML)</label>
<textarea id="scanInput" placeholder="Paste URLs, HTML content, or lists to scan for 3D assets (OBJ, GLTF, Sketchfab, etc)..."></textarea>
</div>
<div class="grid-2">
<div>
<button class="btn" onclick="scanForAssets()">
<i class="fas fa-bolt"></i> Auto-Detect Assets
</button>
<button class="btn btn-pink" onclick="loadHistory()">
<i class="fas fa-history"></i> Load History
</button>
</div>
<div>
<button class="btn btn-green" onclick="exportAssets()">
<i class="fas fa-download"></i> Export Selected
</button>
</div>
</div>
<div class="input-group">
<label>Detected Assets</label>
<ul class="asset-list" id="assetList">
<li style="color: #888; font-style: italic; padding: 10px;">No assets detected yet...</li>
</ul>
</div>
<div class="status-bar" id="scannerStatus">
<i class="fas fa-satellite-dish"></i> Scanner ready...
</div>
</div>
</div>
<!-- File Merger Tab -->
<div id="merger" class="tab-content">
<div class="panel">
<h2 class="panel-title">
<i class="fas fa-code-merge"></i> Project Merger & Optimizer
</h2>
<div class="input-group">
<label>Upload Multiple Files</label>
<input type="file" id="mergeFiles" multiple onchange="handleMergeFiles(event)">
</div>
<div class="input-group">
<label>Merge Options</label>
<div class="grid-options">
<label class="checkbox-label">
<input type="checkbox" id="minify"> Minify JS/CSS
</label>
<label class="checkbox-label">
<input type="checkbox" id="removeComments"> Remove Comments
</label>
<label class="checkbox-label">
<input type="checkbox" id="inlineImages"> Inline Images (Base64)
</label>
</div>
</div>
<button class="btn" onclick="performMerge()">
<i class="fas fa-cogs"></i> Process & Download Bundle
</button>
<div class="input-group">
<label>Merged Output Preview</label>
<textarea id="mergeOutput" readonly placeholder="Merged content will appear here..."></textarea>
</div>
<div class="status-bar" id="mergerStatus">
<i class="fas fa-microchip"></i> Ready to merge files...
</div>
</div>
</div>
<!-- 3D Showroom Tab -->
<div id="3d" class="tab-content">
<div class="panel">
<h2 class="panel-title">
<i class="fas fa-eye"></i> CSS3 3D Viewer
</h2>
<div class="scene-3d">
<div class="cube-container">
<div class="cube-face face-front">CYBER</div>
<div class="cube-face face-back">DECK</div>
<div class="cube-face face-right">DATA</div>
<div class="cube-face face-left">CORE</div>
<div class="cube-face face-top">UPLINK</div>
<div class="cube-face face-bottom">SYS</div>
</div>
</div>
<div class="grid-2">
<div class="input-group">
<label>Cube Opacity: <span id="opacityValue">10%</span></label>
<input type="range" id="opacitySlider" min="0" max="100" value="10"
oninput="updateCubeOpacity(this.value)">
</div>
<div class="input-group">
<label>Animation Speed</label>
<select id="speedSelect" onchange="updateAnimationSpeed(this.value)">
<option value="30s">Glacial</option>
<option value="20s">Slow</option>
<option value="15s" selected>Normal</option>
<option value="5s">Fast</option>
<option value="2s">Hyper</option>
</select>
</div>
</div>
<button class="btn btn-green" onclick="download3DScene()" style="width: 100%; margin-top: 10px;">
<i class="fas fa-download"></i> Download 3D Scene HTML
</button>
<div class="status-bar" id="3dStatus">
<i class="fas fa-cube"></i> 3D Scene Active
</div>
</div>
</div>
</main>
<script>
// Global variables
let currentZip = null;
let extractedFiles = {};
let detectedAssets = [];
let mergedFiles = {};
let activeTab = 'import';
// Tab switching
function switchTab(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
// Remove active class from all nav tabs
document.querySelectorAll('.nav-tab').forEach(tab => {
tab.classList.remove('active');
});
// Show selected tab
document.getElementById(tabName).classList.add('active');
// Add active class to clicked nav tab
// Find the button that calls this function with this tabName
const buttons = document.querySelectorAll('.nav-tab');
buttons.forEach(btn => {
if(btn.getAttribute('onclick').includes(tabName)) {
btn.classList.add('active');
}
});
activeTab = tabName;
}
// Update status messages
function updateStatus(statusId, message, isError = false) {
const el = document.getElementById(statusId);
const color = isError ? 'var(--neon-pink)' : 'var(--neon-green)';
el.style.borderColor = color;
el.style.color = color;
// Get icon class
let iconClass = 'fas fa-check-circle';
if (isError) iconClass = 'fas fa-exclamation-triangle';
else if (message.includes('Loading') || message.includes('Processing')) iconClass = 'fas fa-spinner fa-spin';
else if (message.includes('Ready')) iconClass = 'fas fa-satellite-dish';
el.innerHTML = `<i class="${iconClass}"></i> ${message}`;
}
// Import/Extract CRX/ZIP functions
async function handleCRXFile(event) {
const file = event.target.files[0];
if (!file) return;
updateStatus('importStatus', `Loading ${file.name}...`);
try {
// Read file as array buffer
const arrayBuffer = await file.arrayBuffer();
// Load with JSZip
currentZip = await JSZip.loadAsync(arrayBuffer);
updateStatus('importStatus', 'File loaded. Click "Extract Files" to view contents.');
} catch (error) {
updateStatus('importStatus', `Error: ${error.message}`, true);
console.error(error);
}
}
async function extractFiles() {
if (!currentZip) {
updateStatus('importStatus', 'No file loaded. Please select a CRX/ZIP file first.', true);
return;
}
updateStatus('importStatus', 'Extracting files...');
extractedFiles = {};
let fileTreeHTML = '';
// Convert iterator to array to handle async operations if needed,
// though forEach is synchronous in JSZip for reading metadata
const files = [];
currentZip.forEach((relativePath, file) => {
files.push({ relativePath, file });
});
// Sort folders first, then files
files.sort((a, b) => {
const pathA = a.relativePath.split('/');
const pathB = b.relativePath.split('/');
return pathA[0].localeCompare(pathB[0]);
});
files.forEach(({ relativePath, file }) => {
if (!file.dir) {
extractedFiles[relativePath] = file;
const icon = getFileIcon(relativePath);
fileTreeHTML += `
<div class="file-item" onclick="viewFile('${relativePath.replace(/'/g, "\\'")}')">
<i class="${icon} file-icon"></i>
<span>${relativePath}</span>
</div>
`;
}
});
document.getElementById('fileTree').innerHTML = fileTreeHTML;
document.getElementById('fileTreePanel').style.display = 'block';
updateStatus('importStatus', `Extracted ${Object.keys(extractedFiles).length} files.`);
}
function getFileIcon(filename) {
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'js': 'fab fa-js',
'mjs': 'fab fa-js',
'css': 'fab fa-css3-alt',
'scss': 'fab fa-sass',
'html': 'fab fa-html5',
'json': 'fas fa-file-code',
'png': 'fas fa-file-image',
'jpg': 'fas fa-file-image',
'jpeg': 'fas fa-file-image',
'gif': 'fas fa-file-image',
'svg': 'fas fa-bezier-curve',
'ico': 'fas fa-id-badge',
'xml': 'fas fa-file-code',
'md': 'fab fa-markdown'
};
return icons[ext] || 'fas fa-file';
}
async function viewFile(filename) {
if (!extractedFiles[filename]) return;
updateStatus('importStatus', `Reading ${filename}...`);
try {
const content = await extractedFiles[filename].async('text');
document.getElementById('fileEditor').value = content;
updateStatus('importStatus', `Viewing: ${filename}`);
} catch (e) {
document.getElementById('fileEditor').value = "[Binary or Unreadable File]";
updateStatus('importStatus', `Cannot display binary file: ${filename}`);
}
}
async function repackFiles() {
if (!currentZip) {
updateStatus('importStatus', 'No files to repack.', true);
return;
}
updateStatus('importStatus', 'Repacking files...');
// Generate the zip file
const blob = await currentZip.generateAsync({
type: 'blob',
compression: "DEFLATE",
compressionOptions: { level: 9 }
});
// Download the file
saveAs(blob, 'cyberdeck_repacked.zip');
updateStatus('importStatus', 'Repacked file downloaded successfully.');
}
// Asset Scanner functions
function scanForAssets() {
const input = document.getElementById('scanInput').value;
if (!input.trim()) {
updateStatus('scannerStatus', 'Please provide input to scan.', true);
return;
}
updateStatus('scannerStatus', 'Scanning for assets...');
// Asset detection patterns
const patterns = [
/\.obj$/i,
/\.gltf$/i,
/\.glb$/i,
/\.fbx$/i,
/\.3ds$/i,
/\.stl$/i,
/sketchfab\.com/i,
/free3d\.io/i,
/archive\.org/i,
/raw\.githubusercontent/i,
/\.min\.js$/ // Common for three.js bundles
];
const lines = input.split(/[\s\n,]+/);
const assetList = document.getElementById('assetList');
assetList.innerHTML = '';
let foundCount = 0;
detectedAssets = []; // Reset global list
lines.forEach(line => {
const trimmed = line.trim();
if (!trimmed) return;
const isAsset = patterns.some(pattern => pattern.test(trimmed));
// Also try to catch URLs containing specific keywords even without exact extension
const heuristicMatch = (trimmed.includes('3d-model') || trimmed.includes('download/file')) && trimmed.length > 10;
if ((isAsset || heuristicMatch) && trimmed.length > 5) {
// Avoid duplicates in UI
if (!detectedAssets.includes(trimmed)) {
foundCount++;
const assetId = `asset-${foundCount}`;
detectedAssets.push(trimmed);
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<input type="checkbox" class="asset-checkbox" id="${assetId}" checked data-url="${trimmed.replace(/"/g, '&quot;')}">
<label for="${assetId}" style="cursor: pointer; flex: 1; word-break: break-all;">${trimmed}</label>
`;
assetList.appendChild(li);
// Save to history
saveToHistory(trimmed);
}
}
});
if (foundCount === 0) {
assetList.innerHTML = '<li style="color: #888; padding: 10px;">No assets found matching known patterns.</li>';
}
updateStatus('scannerStatus', `Found ${foundCount} assets.`);
}
function saveToHistory(asset) {
let history = JSON.parse(localStorage.getItem('cyberdeck_assetHistory') || '[]');
if (!history.includes(asset)) {
history.unshift(asset);
if (history.length > 100) history.pop(); // Keep last 100
localStorage.setItem('cyberdeck_assetHistory', JSON.stringify(history));
}
}
function loadHistory() {
const history = JSON.parse(localStorage.getItem('cyberdeck_assetHistory') || '[]');
const assetList = document.getElementById('assetList');
assetList.innerHTML = '';
if (history.length === 0) {
assetList.innerHTML = '<li style="color: #888; padding: 10px;">No history found.</li>';
updateStatus('scannerStatus', 'No history available.', true);
return;
}
history.forEach((asset, index) => {
const assetId = `history-${index}`;
const li = document.createElement('li');
li.className = 'asset-item';
li.innerHTML = `
<input type="checkbox" class="asset-checkbox" id="${assetId}" checked data-url="${asset.replace(/"/g, '&quot;')}">
<label for="${assetId}" style="cursor: pointer; flex: 1; word-break: break-all;">${asset}</label>
`;
assetList.appendChild(li);
});
updateStatus('scannerStatus', `Loaded ${history.length} items from history.`);
}
function exportAssets() {
const checkboxes = document.querySelectorAll('#assetList .asset-checkbox:checked');
const selectedAssets = Array.from(checkboxes).map(cb => cb.getAttribute('data-url') || cb.nextElementSibling.textContent);
if (selectedAssets.length === 0) {
updateStatus('scannerStatus', 'No assets selected.', true);
return;
}
const content = `# Exported 3D Assets\n# Generated by CyberDeck\n\n${selectedAssets.join('\n')}`;
const blob = new Blob([content], { type: 'text/plain' });
saveAs(blob, 'exported_assets.txt');
updateStatus('scannerStatus', `Exported ${selectedAssets.length} assets.`);
}
// File Merger functions
async function handleMergeFiles(event) {
const files = event.target.files;
if (!files || files.length === 0) return;
mergedFiles = {};
updateStatus('mergerStatus', `Loading ${files.length} files...`);
for (let i = 0; i < files.length; i++) {
const file = files[i];
const content = await file.text();
mergedFiles[file.name] = content;
}
updateStatus('mergerStatus', `${files.length} files loaded. Ready to merge.`);
}
async function performMerge() {
if (Object.keys(mergedFiles).length === 0) {
updateStatus('mergerStatus', 'No files to merge. Please upload files first.', true);
return;
}
updateStatus('mergerStatus', 'Processing merge...');
const minify = document.getElementById('minify').checked;
const removeComments = document.getElementById('removeComments').checked;
// Note: inlineImages requires FileReader API for binary data, simplified here
let mergedContent = `/* Merged by CyberDeck */\n`;
let fileCount = 0;
for (const [filename, content] of Object.entries(mergedFiles)) {
fileCount++;
let processed = content;
if (removeComments) {
// Remove HTML comments
processed = processed.replace(/<!--[\s\S]*?-->/g, '');
// Remove JS single line and multi-line comments (simple regex, complex AST is better for production)
processed = processed.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1');
// Remove CSS comments
processed = processed.replace(/\/\*[\s\S]*?\*\//g, '');
}
if (minify) {
// Very basic minification (whitespace removal)
processed = processed.replace(/\s+/g, ' ').trim();
// Remove newlines
processed = processed.replace(/;\s*/g, ';').replace(/\{\s*/g, '{').replace(/\}\s*/g, '}');
}
mergedContent += `\n/* === FILE: ${filename} === */\n${processed}\n`;
}
mergedContent += `\n/* End of merge. Total files: ${fileCount} */`;
document.getElementById('mergeOutput').value = mergedContent;
// Download the merged file
const blob = new Blob([mergedContent], { type: 'text/plain' });
saveAs(blob, 'merged_bundle.txt');
updateStatus('mergerStatus', `Merge complete. ${fileCount} files bundled.`);
}
// 3D Scene functions
function updateCubeOpacity(value) {
document.getElementById('opacityValue').textContent = value + '%';
document.querySelectorAll('.cube-face').forEach(face => {
face.style.background = `rgba(0, 243, 255, ${value / 100})`;
});
}
function updateAnimationSpeed(speed) {
const container = document.querySelector('.cube-container');
container.style.animationDuration = speed;
updateStatus('3dStatus', `Animation speed: ${speed}`);
}
function download3DScene() {
const opacity = document.getElementById('opacitySlider').value;
const speed = document.getElementById('speedSelect').value;
// Get computed colors for the standalone file
const cssVars = `
:root {
--neon-cyan: #00f3ff;
--dark-bg: #