chromacode / static /index.html
HedronCreeper's picture
Update static/index.html
4e6807a verified
<!DOCTYPE html>
<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>