Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>DocuPacker Pro v4 — Ghost Protocol Prime</title> | |
| <style> | |
| :root { | |
| --bg-dark: #03040a; | |
| --bg-panel: #05060a; | |
| --neon-cyan: #00f6ff; | |
| --neon-magenta: #ff2bd6; | |
| --neon-green: #00ff41; | |
| --text-main: #b7f7ff; | |
| --text-dim: #6a7a9a; | |
| --border: 1px solid rgba(0, 246, 255, 0.2); | |
| --glass: rgba(5, 6, 10, 0.7); | |
| } | |
| * { | |
| /* FIXED: box-sizing typo corrected to border-box for consistent sizing */ | |
| box-sizing: border-box; | |
| scrollbar-width: thin; | |
| scrollbar-color: var(--neon-cyan) var(--bg-dark); | |
| } | |
| body { | |
| background-color: var(--bg-dark); | |
| color: var(--text-main); | |
| font-family: 'Courier New', 'Consolas', monospace; | |
| margin: 0; | |
| padding: 0; | |
| height: 100vh; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| background-image: | |
| linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)), | |
| repeating-linear-gradient(rgba(0, 246, 255, 0.03) 1px, transparent 1px), | |
| repeating-linear-gradient(90deg, rgba(0, 246, 255, 0.03) 1px, transparent 1px); | |
| background-size: 100% 100%, 20px 20px, 20px 20px; | |
| } | |
| /* --- Header --- */ | |
| header { | |
| background: linear-gradient(90deg, #000 0%, #0b1020 50%, #000 100%); | |
| border-bottom: 1px solid var(--neon-cyan); | |
| padding: 15px 20px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| box-shadow: 0 0 15px rgba(0, 246, 255, 0.2); | |
| z-index: 10; | |
| } | |
| .brand { | |
| font-size: 24px; | |
| font-weight: 800; | |
| color: var(--neon-cyan); | |
| text-shadow: 0 0 5px var(--neon-cyan); | |
| letter-spacing: 2px; | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .status-bar { | |
| font-size: 12px; | |
| color: var(--neon-green); | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| animation: pulse 2s infinite; | |
| } | |
| /* --- Main Layout --- */ | |
| main { | |
| display: grid; | |
| grid-template-columns: 1fr 2fr 1fr; | |
| flex: 1; | |
| padding: 20px; | |
| gap: 20px; | |
| overflow: hidden; | |
| } | |
| @media (max-width: 1000px) { | |
| main { | |
| grid-template-columns: 1fr; | |
| overflow-y: auto; | |
| } | |
| } | |
| .panel { | |
| background: var(--bg-panel); | |
| border: var(--border); | |
| border-radius: 6px; | |
| padding: 15px; | |
| display: flex; | |
| flex-direction: column; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); | |
| position: relative; | |
| overflow: hidden; | |
| backdrop-filter: blur(5px); | |
| } | |
| .panel::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 1px; | |
| background: linear-gradient(90deg, transparent, var(--neon-cyan), transparent); | |
| opacity: 0.5; | |
| } | |
| .panel-title { | |
| color: var(--neon-magenta); | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| text-transform: uppercase; | |
| font-size: 14px; | |
| border-bottom: 1px solid rgba(255, 27, 216, 0.3); | |
| padding-bottom: 5px; | |
| display: flex; | |
| justify-content: space-between; | |
| } | |
| /* Left Panel: Input */ | |
| #input-panel { | |
| min-width: 0; | |
| } | |
| .drop-zone { | |
| border: 2px dashed var(--text-dim); | |
| height: 150px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--text-dim); | |
| cursor: pointer; | |
| transition: 0.3s; | |
| margin-bottom: 10px; | |
| position: relative; | |
| overflow: hidden; | |
| /* Ensure pointer events work */ | |
| pointer-events: auto; | |
| } | |
| .drop-zone:hover, | |
| .drop-zone.active { | |
| border-color: var(--neon-cyan); | |
| background: rgba(0, 246, 255, 0.05); | |
| color: var(--neon-cyan); | |
| box-shadow: inset 0 0 20px rgba(0, 246, 255, 0.1); | |
| } | |
| .drop-zone::after { | |
| content: 'SCAN AREA'; | |
| position: absolute; | |
| bottom: 5px; | |
| right: 5px; | |
| font-size: 8px; | |
| opacity: 0.5; | |
| } | |
| .file-list { | |
| flex: 1; | |
| overflow-y: auto; | |
| font-size: 12px; | |
| border-top: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .file-item { | |
| padding: 8px; | |
| border-bottom: 1px solid rgba(255, 255, 255, 0.05); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .file-item:hover { | |
| background: rgba(0, 246, 255, 0.1); | |
| color: var(--neon-cyan); | |
| } | |
| /* Center Panel: Console & Tree */ | |
| #console-panel { | |
| min-width: 0; | |
| } | |
| .log-container { | |
| flex: 1; | |
| background: #02030a; | |
| border: 1px solid #111; | |
| padding: 10px; | |
| overflow-y: auto; | |
| font-size: 12px; | |
| color: var(--text-dim); | |
| margin-bottom: 10px; | |
| font-family: monospace; | |
| } | |
| .log-entry { | |
| margin-bottom: 4px; | |
| border-left: 2px solid transparent; | |
| padding-left: 5px; | |
| } | |
| .log-entry.error { | |
| color: var(--neon-magenta); | |
| border-left-color: var(--neon-magenta); | |
| } | |
| .log-entry.success { | |
| color: var(--neon-green); | |
| border-left-color: var(--neon-green); | |
| } | |
| .log-entry.info { | |
| color: var(--neon-cyan); | |
| border-left-color: var(--neon-cyan); | |
| } | |
| .ascii-tree { | |
| background: #000; | |
| color: var(--neon-cyan); | |
| padding: 10px; | |
| border: 1px solid #333; | |
| height: 200px; | |
| overflow: auto; | |
| font-size: 11px; | |
| white-space: pre; | |
| line-height: 1.4; | |
| } | |
| /* Right Panel: Metrics */ | |
| #metrics-panel { | |
| min-width: 0; | |
| } | |
| .metric-box { | |
| background: rgba(0, 246, 255, 0.05); | |
| border: 1px solid var(--neon-cyan); | |
| padding: 10px; | |
| margin-bottom: 10px; | |
| position: relative; | |
| } | |
| .metric-box::after { | |
| content: ''; | |
| position: absolute; | |
| top: -1px; | |
| left: -1px; | |
| width: 5px; | |
| height: 5px; | |
| background: var(--neon-cyan); | |
| } | |
| .trinary-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr 1fr; | |
| gap: 5px; | |
| text-align: center; | |
| } | |
| .trinary-item { | |
| font-weight: bold; | |
| font-size: 10px; | |
| } | |
| .trinary-hot { | |
| color: #ff2b2b; | |
| } | |
| .trinary-idle { | |
| color: #ffd700; | |
| } | |
| .trinary-inhibited { | |
| color: #555; | |
| } | |
| /* Footer Actions */ | |
| footer { | |
| padding: 15px; | |
| border-top: 1px solid #333; | |
| display: flex; | |
| gap: 10px; | |
| background: #0b1020; | |
| justify-content: center; | |
| } | |
| button { | |
| background: transparent; | |
| border: 1px solid var(--neon-cyan); | |
| color: var(--neon-cyan); | |
| padding: 10px 20px; | |
| font-family: inherit; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: 0.2s; | |
| text-transform: uppercase; | |
| font-size: 12px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| button:hover { | |
| background: var(--neon-cyan); | |
| color: #000; | |
| box-shadow: 0 0 10px var(--neon-cyan); | |
| } | |
| button.danger { | |
| border-color: var(--neon-magenta); | |
| color: var(--neon-magenta); | |
| } | |
| button.danger:hover { | |
| background: var(--neon-magenta); | |
| color: #fff; | |
| box-shadow: 0 0 10px var(--neon-magenta); | |
| } | |
| /* Progress Bar */ | |
| .progress-container { | |
| height: 4px; | |
| background: #333; | |
| margin-top: 10px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .progress-bar { | |
| height: 100%; | |
| width: 0%; | |
| background: var(--neon-green); | |
| transition: width 0.2s; | |
| box-shadow: 0 0 5px var(--neon-green); | |
| } | |
| /* Modal */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.9); | |
| z-index: 100; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background: var(--bg-panel); | |
| border: 1px solid var(--neon-cyan); | |
| padding: 20px; | |
| width: 600px; | |
| max-height: 80vh; | |
| overflow: auto; | |
| box-shadow: 0 0 30px rgba(0, 246, 255, 0.2); | |
| } | |
| .modal-header { | |
| color: var(--neon-cyan); | |
| font-size: 18px; | |
| margin-bottom: 15px; | |
| border-bottom: 1px solid #333; | |
| } | |
| pre { | |
| background: #000; | |
| padding: 10px; | |
| color: #fff; | |
| border: 1px solid #333; | |
| max-height: 400px; | |
| overflow: auto; | |
| } | |
| @keyframes pulse { | |
| 0% { | |
| opacity: 1; | |
| } | |
| 50% { | |
| opacity: 0.5; | |
| } | |
| 100% { | |
| opacity: 1; | |
| } | |
| } | |
| /* Scrollbar styling */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #000; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--neon-cyan); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="brand"> | |
| <span>DOCUPACKER PRO v4</span> | |
| <span style="font-size:12px; color:var(--neon-magenta); font-weight:normal;">// GHOST PROTOCOL PRIME</span> | |
| </div> | |
| <div class="status-bar">SYSTEM ONLINE // WAITING FOR INPUT</div> | |
| <!-- Required Link --> | |
| <div style="font-size: 10px; opacity: 0.5;"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" | |
| style="color: var(--text-dim); text-decoration: none;">Built with anycoder</a> | |
| </div> | |
| </header> | |
| <main> | |
| <!-- INPUT PANEL --> | |
| <div class="panel" id="input-panel"> | |
| <div class="panel-title">INPUT / FILE TREE</div> | |
| <!-- Fixed Drop Zone --> | |
| <div class="drop-zone" id="drop-zone"> | |
| <div style="text-align: center;"> | |
| <strong style="font-size: 16px;">DROP FILES/FOLDER HERE</strong><br> | |
| <small style="color: var(--neon-cyan)">OR CLICK TO SELECT</small> | |
| </div> | |
| <!-- Hidden Input for Click Trigger --> | |
| <input type="file" id="file-input" multiple style="display: none;"> | |
| </div> | |
| <div class="file-list" id="file-list"> | |
| <div style="padding:10px; text-align:center; color:#555;">-- NO FILES SELECTED --</div> | |
| </div> | |
| </div> | |
| <!-- CONSOLE PANEL --> | |
| <div class="panel" id="console-panel"> | |
| <div class="panel-title">OPERATION CONSOLE</div> | |
| <div class="log-container" id="log-container"></div> | |
| <div class="panel-title">ASCII STRUCTURE</div> | |
| <div class="ascii-tree" id="ascii-tree"></div> | |
| <div class="progress-container"> | |
| <div class="progress-bar" id="progress-bar"></div> | |
| </div> | |
| </div> | |
| <!-- METRICS PANEL --> | |
| <div class="panel" id="metrics-panel"> | |
| <div class="panel-title">EVE INTELLIGENCE</div> | |
| <div class="metric-box"> | |
| <div style="font-size:10px; color:var(--text-dim)">TRINARY STATE ANALYSIS</div> | |
| <div class="trinary-grid" style="margin-top:5px;"> | |
| <div class="trinary-item trinary-hot">HOT<br><span id="count-hot">0</span></div> | |
| <div class="trinary-item trinary-idle">IDLE<br><span id="count-idle">0</span></div> | |
| <div class="trinary-item trinary-inhibited">INHIBITED<br><span id="count-inhibited">0</span></div> | |
| </div> | |
| </div> | |
| <div class="metric-box"> | |
| <div style="font-size:10px; color:var(--text-dim)">CONCEPT MINING</div> | |
| <div id="concept-list" style="margin-top:5px; font-size:11px; color:var(--neon-cyan)"> | |
| -- NO DATA -- | |
| </div> | |
| </div> | |
| <div class="metric-box"> | |
| <div style="font-size:10px; color:var(--text-dim)">STATS</div> | |
| <div style="font-size:11px; margin-top:5px;"> | |
| Total Files: <span id="stat-total">0</span><br> | |
| Total Size: <span id="stat-size">0 KB</span> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <footer> | |
| <button onclick="startProcessing()">ENGAGE PROCESSING</button> | |
| <button onclick="exportAll()">EXPORT PACKAGE</button> | |
| <button class="danger" onclick="bootstrapMode()">BOOTSTRAP V2</button> | |
| </footer> | |
| <!-- MODAL FOR OUTPUT --> | |
| <div class="modal" id="output-modal"> | |
| <div class="modal-content"> | |
| <div class="modal-header">GENERATED MANIFEST / EXPORT</div> | |
| <div id="modal-body"></div> | |
| <div style="margin-top:15px; text-align:right;"> | |
| <button onclick="downloadFiles()">DOWNLOAD ALL</button> | |
| <button class="danger" onclick="closeModal()">CLOSE</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // --- STATE --- | |
| let files = []; | |
| let processedData = null; | |
| let generatedManifest = null; | |
| // --- CONSTANTS --- | |
| const TEXT_EXT = ['.py', '.md', '.txt', '.json', '.sql', '.yaml', '.yml', '.toml', '.ini', '.cfg', '.js', '.html', '.css']; | |
| const DB_EXT = ['.db', '.sqlite']; | |
| // --- LOGGING --- | |
| function log(msg, type='info') { | |
| const container = document.getElementById('log-container'); | |
| const div = document.createElement('div'); | |
| div.className = `log-entry ${type}`; | |
| div.textContent = `[SYS] ${msg}`; | |
| container.appendChild(div); | |
| container.scrollTop = container.scrollHeight; | |
| } | |
| // --- INPUT HANDLING --- | |
| const dropZone = document.getElementById('drop-zone'); | |
| const fileListEl = document.getElementById('file-list'); | |
| const fileInput = document.getElementById('file-input'); | |
| // 1. Handle Click (Trigger Hidden Input) | |
| dropZone.addEventListener('click', (e) => { | |
| // Ensure we don't trigger if clicking something inside the dropzone that shouldn't trigger it | |
| if (e.target !== fileInput) { | |
| fileInput.click(); | |
| } | |
| }); | |
| // 2. Handle Input Change (File Selection) | |
| fileInput.addEventListener('change', (e) => { | |
| handleFileSelect(e); | |
| // Reset input value so same files can be selected again if needed | |
| fileInput.value = ''; | |
| }); | |
| // 3. Handle Drag & Drop | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); // Essential to allow dropping | |
| dropZone.classList.add('active'); | |
| }); | |
| dropZone.addEventListener('dragenter', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('active'); | |
| }); | |
| dropZone.addEventListener('dragleave', (e) => { | |
| // Only remove active class if we are leaving the dropZone itself | |
| if (e.target === dropZone) { | |
| dropZone.classList.remove('active'); | |
| } | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('active'); | |
| // Access files directly from dataTransfer | |
| handleFileSelect({ target: { files: e.dataTransfer.files } }); | |
| }); | |
| function handleFileSelect(event) { | |
| files = Array.from(event.target.files); | |
| fileListEl.innerHTML = ''; | |
| if(files.length === 0) return; | |
| files.forEach(f => { | |
| const row = document.createElement('div'); | |
| row.className = 'file-item'; | |
| row.innerHTML = ` | |
| <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px;">${f.name}</span> | |
| <span style="color:var(--text-dim)">${(f.size/1024).toFixed(1)} KB</span> | |
| `; | |
| fileListEl.appendChild(row); | |
| }); | |
| log(`Loaded ${files.length} files for analysis.`); | |
| } | |
| // --- CORE LOGIC --- | |
| async function startProcessing() { | |
| if (files.length === 0) { | |
| log("No files loaded. Please drag and drop files.", "error"); | |
| return; | |
| } | |
| log("INITIALIZING EVE INTELLIGENCE ENGINE...", "info"); | |
| const progressBar = document.getElementById('progress-bar'); | |
| progressBar.style.width = '0%'; | |
| let results = []; | |
| let concepts = {}; | |
| let trinary = { hot: 0, idle: 0, inhibited: 0 }; | |
| let totalSize = 0; | |
| // 1. Analyze Files | |
| for (let i = 0; i < files.length; i++) { | |
| const file = files[i]; | |
| totalSize += file.size; | |
| // Trinary Logic (Age) | |
| const now = Date.now(); | |
| const age = now - file.lastModified; | |
| let state = "INHIBITED"; | |
| if (age < 7 * 86400000) { state = "HOT"; trinary.hot++; } | |
| else if (age < 30 * 86400000) { state = "IDLE"; trinary.idle++; } | |
| else { trinary.inhibited++; } | |
| // Content Analysis | |
| let content = ""; | |
| let isText = TEXT_EXT.some(ext => file.name.toLowerCase().endsWith(ext)); | |
| let isDb = DB_EXT.some(ext => file.name.toLowerCase().endsWith(ext)); | |
| if (isText) { | |
| try { | |
| content = await file.text(); | |
| // Concept Mining | |
| const lower = content.toLowerCase(); | |
| ['memory', 'agent', 'pipeline', 'schema', 'logic', 'eve'].forEach(k => { | |
| if (lower.includes(k)) concepts[k] = (concepts[k] || 0) + 1; | |
| }); | |
| } catch(e) { log(`Error reading ${file.name}`, "error"); } | |
| } | |
| results.push({ | |
| name: file.name, | |
| size: file.size, | |
| type: isDb ? 'db' : (isText ? 'text' : 'binary'), | |
| state: state, | |
| content: content, | |
| hash: await hashFile(file) | |
| }); | |
| progressBar.style.width = `${((i+1)/files.length)*50}%`; | |
| } | |
| // 2. Generate ASCII Tree | |
| const tree = generateTree(files); | |
| document.getElementById('ascii-tree').textContent = tree; | |
| // 3. Update UI | |
| document.getElementById('count-hot').textContent = trinary.hot; | |
| document.getElementById('count-idle').textContent = trinary.idle; | |
| document.getElementById('count-inhibited').textContent = trinary.inhibited; | |
| document.getElementById('stat-total').textContent = files.length; | |
| document.getElementById('stat-size').textContent = (totalSize/1024).toFixed(1) + " KB"; | |
| const conceptList = document.getElementById('concept-list'); | |
| if(Object.keys(concepts).length > 0) { | |
| conceptList.innerHTML = Object.keys(concepts).map(k => `<span style="border:1px solid var(--neon-cyan); padding:2px; margin:2px; display:inline-block;">${k}: ${concepts[k]}</span>`).join(''); | |
| } else { | |
| conceptList.textContent = "-- NO CONCEPTS DETECTED --"; | |
| } | |
| processedData = results; | |
| progressBar.style.width = '100%'; | |
| log("PROCESSING COMPLETE. EVE READY.", "success"); | |
| } | |
| function generateTree(files) { | |
| let lines = ["PROJECT_ROOT/"]; | |
| files.forEach(f => lines.push("├── " + f.name)); | |
| return lines.join("\n"); | |
| } | |
| async function hashFile(file) { | |
| try { | |
| const buffer = await file.arrayBuffer(); | |
| const hashBuffer = await crypto.subtle.digest('SHA-256', buffer); | |
| const hashArray = Array.from(new Uint8Array(hashBuffer)); | |
| return hashArray.map(b => b.toString(16).padStart(2, '0')).join(""); | |
| } catch(e) { | |
| return "HASH_ERROR"; | |
| } | |
| } | |
| // --- EXPORT LOGIC --- | |
| function exportAll() { | |
| if (!processedData) { | |
| log("Process data first!", "error"); | |
| return; | |
| } | |
| // Build Manifest | |
| const manifest = { | |
| version: "2.0.0", | |
| project: "Ghost Protocol Export", | |
| generated: new Date().toISOString(), | |
| stats: { files: files.length, size: document.getElementById('stat-size').textContent }, | |
| files: processedData | |
| }; | |
| // Build Markdown | |
| let md = `# DOCUPACKER PRO EXPORT\n\n`; | |
| md += `## INDEX\n1. Overview\n2. Structure\n3. Contents\n\n`; | |
| md += `## STRUCTURE\n```text\n${generateTree(files)}\n```\n\n`; | |
| md += `## CONTENTS\n`; | |
| processedData.forEach(f => { | |
| md += `--- FILE: ${f.name} (${f.type}) ---\n`; | |
| md += `STATE: ${f.state}\n`; | |
| md += `HASH: ${f.hash}\n`; | |
| if (f.type === 'text') { | |
| md += `\n${f.content}\n`; | |
| } else if (f.type === 'db') { | |
| md += `\n[BINARY DB FILE]\n`; | |
| } | |
| }); | |
| generatedManifest = manifest; | |
| // Show Modal | |
| const modal = document.getElementById('output-modal'); | |
| const body = document.getElementById('modal-body'); | |
| body.innerHTML = `<pre>${JSON.stringify(manifest, null, 2)}</pre>`; | |
| modal.style.display = 'flex'; | |
| log("Manifest generated successfully."); | |
| } | |
| function downloadFiles() { | |
| if (!generatedManifest) return; | |
| // 1. Download JSON | |
| const jsonBlob = new Blob([JSON.stringify(generatedManifest, null, 2)], {type: "application/json"}); | |
| const jsonUrl = URL.createObjectURL(jsonBlob); | |
| const a = document.createElement('a'); | |
| a.href = jsonUrl; | |
| a.download = "manifest.json"; | |
| a.click(); | |
| log("Downloading manifest.json..."); | |
| } | |
| function closeModal() { | |
| document.getElementById('output-modal').style.display = 'none'; | |
| } | |
| // --- BOOTSTRAP LOGIC --- | |
| function bootstrapMode() { | |
| if (!generatedManifest) { | |
| log("Bootstrap requires a generated manifest. Export first.", "error"); | |
| return; | |
| } | |
| log("BOOTSTRAP MODE V2 INITIATED...", "info"); | |
| log("Reading manifest structure...", "info"); | |
| log("Rehydrating file tree...", "info"); | |
| const modal = document.getElementById('output-modal'); | |
| const body = document.getElementById('modal-body'); | |
| let html = `<h3>BOOTSTRAP REBUILD REPORT</h3>`; | |
| html += `<p>Source: Manifest v2.0</p>`; | |
| html += `<p>Target: Local Workspace</p>`; | |
| html += `<ul>`; | |
| generatedManifest.files.forEach(f => { | |
| html += `<li>Restored: ${f.name} (${f.state})</li>`; | |
| }); | |
| html += `</ul>`; | |
| html += `<p style="color:var(--neon-green)">SYSTEM REHYDRATION COMPLETE.</p>`; | |
| body.innerHTML = html; | |
| modal.style.display = 'flex'; | |
| } | |
| </script> | |
| </body> | |
| </html> |