Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>ChromaCode</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| background: #080810; | |
| color: #e0e0f0; | |
| font-family: 'Segoe UI', sans-serif; | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 2rem; | |
| gap: 2rem; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| background: linear-gradient(90deg, #f472b6, #a78bfa, #60a5fa); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| letter-spacing: -1px; | |
| } | |
| p.sub { color: #555570; font-size: 0.9rem; margin-top: -1.5rem; text-align: center; } | |
| .row { display: flex; gap: 1.5rem; width: 100%; max-width: 960px; flex-wrap: wrap; } | |
| .card { | |
| background: #10101e; | |
| border: 1px solid #222238; | |
| border-radius: 16px; | |
| padding: 1.5rem; | |
| flex: 1; | |
| min-width: 300px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .card h2 { font-size: 0.85rem; font-weight: 600; color: #a0a0c0; letter-spacing: 0.08em; text-transform: uppercase; } | |
| .tabs { display: flex; gap: 0.5rem; } | |
| .tab { | |
| flex: 1; | |
| padding: 0.5rem; | |
| background: #080810; | |
| border: 1px solid #222238; | |
| border-radius: 8px; | |
| color: #555570; | |
| font-size: 0.85rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| text-align: center; | |
| transition: all 0.2s; | |
| } | |
| .tab.active { border-color: #7c3aed; color: #a78bfa; background: #1a0a2e; } | |
| .panel { display: none; flex-direction: column; gap: 1rem; } | |
| .panel.active { display: flex; } | |
| textarea { | |
| width: 100%; | |
| background: #080810; | |
| border: 1px solid #222238; | |
| border-radius: 10px; | |
| color: #e0e0f0; | |
| font-size: 0.9rem; | |
| padding: 0.75rem 1rem; | |
| outline: none; | |
| transition: border-color 0.2s; | |
| resize: vertical; | |
| min-height: 110px; | |
| } | |
| textarea:focus { border-color: #7c3aed; } | |
| .upload-zone { | |
| border: 2px dashed #222238; | |
| border-radius: 10px; | |
| padding: 1.5rem; | |
| text-align: center; | |
| cursor: pointer; | |
| transition: border-color 0.2s; | |
| font-size: 0.85rem; | |
| color: #555570; | |
| } | |
| .upload-zone:hover { border-color: #7c3aed; } | |
| .upload-zone input { display: none; } | |
| .upload-zone.has-file { border-color: #22c55e; color: #22c55e; } | |
| .upload-zone.has-error { border-color: #ef4444; color: #ef4444; } | |
| button { | |
| width: 100%; | |
| padding: 0.75rem; | |
| background: linear-gradient(135deg, #7c3aed, #3b82f6); | |
| border: none; | |
| border-radius: 10px; | |
| color: white; | |
| font-size: 0.95rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: opacity 0.2s; | |
| } | |
| button:hover { opacity: 0.85; } | |
| button:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .btn-row { display: flex; gap: 0.5rem; } | |
| .btn-small { | |
| flex: 1; | |
| padding: 0.5rem; | |
| background: #1a1a30; | |
| border: 1px solid #222238; | |
| border-radius: 8px; | |
| color: #a0a0c0; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| width: auto; | |
| } | |
| .btn-small:hover { border-color: #7c3aed; color: #a78bfa; opacity: 1; } | |
| .status { font-size: 0.8rem; color: #555570; text-align: center; min-height: 1em; } | |
| .status.error { color: #ef4444; } | |
| .status.success { color: #22c55e; } | |
| .preview { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; } | |
| .preview img { max-width: 160px; border-radius: 8px; border: 1px solid #222238; image-rendering: pixelated; } | |
| .download { font-size: 0.8rem; color: #a78bfa; cursor: pointer; text-decoration: underline; } | |
| .size-tag { font-size: 0.75rem; color: #444460; } | |
| .decoded-text { | |
| background: #080810; | |
| border: 1px solid #222238; | |
| border-radius: 10px; | |
| padding: 0.75rem 1rem; | |
| font-size: 0.9rem; | |
| color: #e0e0f0; | |
| word-break: break-all; | |
| white-space: pre-wrap; | |
| max-height: 200px; | |
| overflow-y: auto; | |
| display: none; | |
| } | |
| .decoded-image { display: none; flex-direction: column; align-items: center; gap: 0.5rem; } | |
| .decoded-image img { max-width: 100%; border-radius: 8px; border: 1px solid #222238; } | |
| .html-preview { | |
| display: none; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .html-preview iframe { | |
| width: 100%; | |
| height: 300px; | |
| border-radius: 8px; | |
| border: 1px solid #222238; | |
| background: white; | |
| } | |
| .code-preview { | |
| display: none; | |
| flex-direction: column; | |
| gap: 0.5rem; | |
| } | |
| .code-preview pre { | |
| background: #080810; | |
| border: 1px solid #222238; | |
| border-radius: 8px; | |
| padding: 0.75rem; | |
| font-size: 0.8rem; | |
| color: #a78bfa; | |
| overflow-x: auto; | |
| max-height: 200px; | |
| white-space: pre-wrap; | |
| } | |
| .lang-badge { | |
| font-size: 0.7rem; | |
| padding: 0.2rem 0.5rem; | |
| border-radius: 4px; | |
| background: #1a0a2e; | |
| color: #a78bfa; | |
| border: 1px solid #7c3aed; | |
| align-self: flex-start; | |
| } | |
| .pdf-preview { display: none; flex-direction: column; gap: 0.5rem; font-size: 0.85rem; color: #a0a0c0; text-align: center; } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>ChromaCode</h1> | |
| <p class="sub">Steganography. Hidden in plain sight.</p> | |
| <div class="row"> | |
| <div class="card"> | |
| <h2>Encode</h2> | |
| <div class="tabs"> | |
| <div class="tab active" onclick="switchTab('text')">Text</div> | |
| <div class="tab" onclick="switchTab('file')">Image / PDF</div> | |
| </div> | |
| <div class="panel active" id="panel-text"> | |
| <textarea id="encodeInput" placeholder="Type a message, HTML, JS, Python..."></textarea> | |
| </div> | |
| <div class="panel" id="panel-file"> | |
| <div class="upload-zone" id="uploadZone" onclick="document.getElementById('fileUpload').click()"> | |
| <input type="file" id="fileUpload" accept="image/*,.pdf" onchange="handleFileUpload(this)"> | |
| <span id="uploadLabel">Click to upload image or PDF (max 5MB)</span> | |
| </div> | |
| </div> | |
| <button id="encodeBtn" onclick="encode()">Encode</button> | |
| <div class="status" id="encodeStatus"></div> | |
| <div class="preview" id="encodePreview"></div> | |
| </div> | |
| <div class="card"> | |
| <h2>Decode</h2> | |
| <div class="upload-zone" id="decodeZone" onclick="document.getElementById('decodeFile').click()"> | |
| <input type="file" id="decodeFile" accept="image/png" onchange="handleDecodeUpload(this)"> | |
| <span id="decodeLabel">Upload a ChromaCode PNG</span> | |
| </div> | |
| <button id="decodeBtn" onclick="decodeImage()">Extract</button> | |
| <div class="status" id="decodeStatus"></div> | |
| <div class="decoded-text" id="decodedText"></div> | |
| <div class="html-preview" id="htmlPreview"> | |
| <span class="lang-badge">HTML</span> | |
| <iframe id="htmlFrame" sandbox="allow-scripts"></iframe> | |
| <div class="btn-row"> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.txt')">Download .txt</button> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.html')">Download .html</button> | |
| </div> | |
| </div> | |
| <div class="code-preview" id="jsPreview"> | |
| <span class="lang-badge">JavaScript</span> | |
| <pre id="jsCode"></pre> | |
| <div class="btn-row"> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.txt')">Download .txt</button> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.js')">Download .js</button> | |
| </div> | |
| </div> | |
| <div class="code-preview" id="pyPreview"> | |
| <span class="lang-badge">Python</span> | |
| <pre id="pyCode"></pre> | |
| <div class="btn-row"> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.txt')">Download .txt</button> | |
| <button class="btn-small" onclick="downloadDecoded('decoded.py')">Download .py</button> | |
| </div> | |
| </div> | |
| <div class="decoded-image" id="decodedImage"> | |
| <img id="decodedImg" src="" alt="decoded"> | |
| <span class="download" onclick="downloadBinary('decoded.png')">Download Image</span> | |
| </div> | |
| <div class="pdf-preview" id="pdfPreview"> | |
| <span class="lang-badge">PDF</span> | |
| <p>PDF extracted successfully.</p> | |
| <button class="btn-small" onclick="downloadBinary('decoded.pdf')">Download PDF</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| let activeTab = "text"; | |
| let uploadedFile = null; | |
| let decodedBinaryData = null; | |
| let decodedBinaryMime = null; | |
| let decodedTextContent = null; | |
| let encodedSrc = null; | |
| function switchTab(tab) { | |
| activeTab = tab; | |
| document.querySelectorAll(".tab").forEach((t, i) => { | |
| t.classList.toggle("active", (i === 0 && tab === "text") || (i === 1 && tab === "file")); | |
| }); | |
| document.getElementById("panel-text").classList.toggle("active", tab === "text"); | |
| document.getElementById("panel-file").classList.toggle("active", tab === "file"); | |
| document.getElementById("encodeStatus").textContent = ""; | |
| document.getElementById("encodePreview").innerHTML = ""; | |
| } | |
| function handleFileUpload(input) { | |
| const file = input.files[0]; | |
| const zone = document.getElementById("uploadZone"); | |
| const label = document.getElementById("uploadLabel"); | |
| if (!file) return; | |
| if (file.size > 5 * 1024 * 1024) { | |
| zone.className = "upload-zone has-error"; | |
| label.textContent = `Too large: ${(file.size/1024/1024).toFixed(2)}MB. Max is 5MB.`; | |
| uploadedFile = null; | |
| return; | |
| } | |
| uploadedFile = file; | |
| zone.className = "upload-zone has-file"; | |
| label.textContent = `${file.name} (${(file.size/1024).toFixed(1)}KB)`; | |
| } | |
| function handleDecodeUpload(input) { | |
| const file = input.files[0]; | |
| if (file) document.getElementById("decodeLabel").textContent = file.name; | |
| } | |
| async function encode() { | |
| const status = document.getElementById("encodeStatus"); | |
| const preview = document.getElementById("encodePreview"); | |
| const btn = document.getElementById("encodeBtn"); | |
| status.className = "status"; | |
| preview.innerHTML = ""; | |
| btn.disabled = true; | |
| if (activeTab === "text") { | |
| const text = document.getElementById("encodeInput").value.trim(); | |
| if (!text) { status.textContent = "Enter some text first."; btn.disabled = false; return; } | |
| status.textContent = "Encoding..."; | |
| const res = await fetch("/encode/text", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ text }) | |
| }); | |
| const data = await res.json(); | |
| if (data.image) { | |
| showEncodedResult(data.image, data.size); | |
| status.className = "status success"; | |
| status.textContent = "Encoded!"; | |
| } else { | |
| status.className = "status error"; | |
| status.textContent = "Error: " + (data.error || "Unknown"); | |
| } | |
| } else { | |
| if (!uploadedFile) { status.textContent = "Upload a file first."; btn.disabled = false; return; } | |
| status.textContent = "Encoding..."; | |
| const form = new FormData(); | |
| form.append("file", uploadedFile); | |
| const res = await fetch("/encode/file", { method: "POST", body: form }); | |
| const data = await res.json(); | |
| if (data.image) { | |
| showEncodedResult(data.image, data.size); | |
| status.className = "status success"; | |
| status.textContent = "Encoded!"; | |
| } else { | |
| status.className = "status error"; | |
| status.textContent = "Error: " + (data.error || "Unknown"); | |
| } | |
| } | |
| btn.disabled = false; | |
| } | |
| function showEncodedResult(b64, size) { | |
| encodedSrc = "data:image/png;base64," + b64; | |
| const preview = document.getElementById("encodePreview"); | |
| const img = document.createElement("img"); | |
| img.src = encodedSrc; | |
| const dl = document.createElement("span"); | |
| dl.className = "download"; | |
| dl.textContent = "Download ChromaCode PNG"; | |
| dl.onclick = () => { const a = document.createElement("a"); a.href = encodedSrc; a.download = "chromacode.png"; a.click(); }; | |
| const sz = document.createElement("span"); | |
| sz.className = "size-tag"; | |
| sz.textContent = `Canvas: ${size} px`; | |
| preview.appendChild(img); | |
| preview.appendChild(dl); | |
| preview.appendChild(sz); | |
| } | |
| function detectTextType(text) { | |
| const t = text.trim(); | |
| if (/^[\s\S]*<html[\s\S]*>[\s\S]*<\/html>/i.test(t) || /^<!DOCTYPE html/i.test(t) || (/<[a-z][\s\S]*>/i.test(t) && /<\/[a-z]+>/i.test(t))) return "html"; | |
| if (/^(import |from |def |class |if __name__|print\(|#!\/usr\/bin\/env python)/m.test(t)) return "python"; | |
| if (/^(const |let |var |function |class |import |export |=>|\/\/|console\.log)/m.test(t)) return "js"; | |
| return "text"; | |
| } | |
| function hideAllOutputs() { | |
| ["decodedText","htmlPreview","jsPreview","pyPreview","decodedImage","pdfPreview"].forEach(id => { | |
| const el = document.getElementById(id); | |
| el.style.display = "none"; | |
| }); | |
| } | |
| async function decodeImage() { | |
| const file = document.getElementById("decodeFile").files[0]; | |
| if (!file) return; | |
| const status = document.getElementById("decodeStatus"); | |
| const btn = document.getElementById("decodeBtn"); | |
| hideAllOutputs(); | |
| status.className = "status"; | |
| btn.disabled = true; | |
| status.textContent = "Extracting..."; | |
| const form = new FormData(); | |
| form.append("file", file); | |
| const res = await fetch("/decode", { method: "POST", body: form }); | |
| const data = await res.json(); | |
| if (data.type === "text") { | |
| const text = data.text; | |
| decodedTextContent = text; | |
| const kind = detectTextType(text); | |
| if (kind === "html") { | |
| const frame = document.getElementById("htmlFrame"); | |
| frame.srcdoc = text; | |
| document.getElementById("htmlPreview").style.display = "flex"; | |
| status.className = "status success"; | |
| status.textContent = "HTML detected — previewing below."; | |
| } else if (kind === "js") { | |
| document.getElementById("jsCode").textContent = text; | |
| document.getElementById("jsPreview").style.display = "flex"; | |
| status.className = "status success"; | |
| status.textContent = "JavaScript detected."; | |
| } else if (kind === "python") { | |
| document.getElementById("pyCode").textContent = text; | |
| document.getElementById("pyPreview").style.display = "flex"; | |
| status.className = "status success"; | |
| status.textContent = "Python detected."; | |
| } else { | |
| const out = document.getElementById("decodedText"); | |
| out.textContent = text; | |
| out.style.display = "block"; | |
| status.className = "status success"; | |
| status.textContent = "Text extracted!"; | |
| } | |
| } else if (data.type === "image") { | |
| const src = "data:image/png;base64," + data.image; | |
| decodedBinaryData = data.image; | |
| decodedBinaryMime = "image/png"; | |
| document.getElementById("decodedImg").src = src; | |
| document.getElementById("decodedImage").style.display = "flex"; | |
| status.className = "status success"; | |
| status.textContent = "Image extracted!"; | |
| } else if (data.type === "pdf") { | |
| decodedBinaryData = data.pdf; | |
| decodedBinaryMime = "application/pdf"; | |
| document.getElementById("pdfPreview").style.display = "flex"; | |
| status.className = "status success"; | |
| status.textContent = "PDF extracted!"; | |
| } else { | |
| status.className = "status error"; | |
| status.textContent = "Error: " + (data.error || "Unknown"); | |
| } | |
| btn.disabled = false; | |
| } | |
| function downloadDecoded(filename) { | |
| const blob = new Blob([decodedTextContent], { type: "text/plain" }); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = filename; | |
| a.click(); | |
| } | |
| function downloadBinary(filename) { | |
| const bytes = atob(decodedBinaryData); | |
| const arr = new Uint8Array(bytes.length); | |
| for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i); | |
| const blob = new Blob([arr], { type: decodedBinaryMime }); | |
| const a = document.createElement("a"); | |
| a.href = URL.createObjectURL(blob); | |
| a.download = filename; | |
| a.click(); | |
| } | |
| </script> | |
| </body> | |
| </html> |