Skip to content

Instantly share code, notes, and snippets.

@mstatt
Created August 6, 2025 08:55
Show Gist options
  • Select an option

  • Save mstatt/cea118190fa371b8e37631fa9ef2b916 to your computer and use it in GitHub Desktop.

Select an option

Save mstatt/cea118190fa371b8e37631fa9ef2b916 to your computer and use it in GitHub Desktop.
The single page html for implementing the FALCONLENS Image Forensic Solution
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FALCONLENS // Image Forensics v1.2</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Roboto+Mono:wght@300;400&display=swap');
:root {
--primary-color: #ff003c;
--secondary-color: #00e5ff;
--background-color: #0a0a0a;
--text-color: #e0e0e0;
--border-color: rgba(255, 0, 60, 0.4);
--glow-color: rgba(255, 0, 60, 0.7);
--container-bg: rgba(15, 15, 15, 0.85);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
background-color: var(--background-color);
color: var(--text-color);
font-family: 'Roboto Mono', monospace;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 2rem;
background-image:
linear-gradient(rgba(0, 229, 255, 0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 229, 255, 0.04) 1px, transparent 1px);
background-size: 25px 25px;
}
.container {
width: 100%;
max-width: 900px;
background: var(--container-bg);
border: 1px solid var(--border-color);
padding: 2rem;
backdrop-filter: blur(5px);
}
header {
text-align: center;
margin-bottom: 2rem;
padding: 1.5rem;
border-bottom: 1px solid var(--border-color);
}
header h1 {
font-family: 'Orbitron', sans-serif;
color: var(--primary-color);
font-size: 2.5rem;
letter-spacing: 4px;
text-shadow: 0 0 15px var(--glow-color);
}
header p { margin-top: 0.5rem; font-size: 0.9rem; max-width: 700px; margin-left: auto; margin-right: auto; }
.mode-switcher { display: flex; border-bottom: 1px solid var(--border-color); margin-bottom: 2rem; }
.tab-btn {
flex-grow: 1;
padding: 1rem;
background: transparent;
border: none;
color: var(--text-color);
font-family: 'Orbitron', sans-serif;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.3s ease;
border-bottom: 3px solid transparent;
}
.tab-btn:hover { background: rgba(255, 0, 60, 0.1); }
.tab-btn.active { color: var(--primary-color); border-bottom-color: var(--primary-color); text-shadow: 0 0 5px var(--glow-color); }
.analysis-form { display: none; }
.analysis-form.active { display: block; }
.upload-grid { display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-bottom: 1.5rem; }
.upload-grid.two-cols { grid-template-columns: 1fr 1fr; }
.drop-zone {
border: 2px dashed var(--border-color);
padding: 1rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
min-height: 150px;
}
.drop-zone:hover, .drop-zone.drag-over {
border-color: var(--primary-color);
background: rgba(255, 0, 60, 0.1);
text-shadow: 0 0 5px var(--glow-color);
}
.drop-zone h3 { color: var(--secondary-color); margin-bottom: 0.5rem; }
.drop-zone p { font-size: 0.8rem; }
.drop-zone .file-name { color: var(--primary-color); font-style: italic; margin-top: 0.5rem; height: 1.2em; word-break: break-all; }
.drop-zone input[type="file"] { display: none; }
#submit-btn {
display: block; width: 100%;
background-color: transparent;
color: var(--primary-color);
border: 2px solid var(--primary-color);
padding: 1rem 2.5rem;
font-family: 'Orbitron', monospace;
font-size: 1.2rem;
font-weight: 700;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 2px;
transition: all 0.3s ease;
}
#submit-btn:hover:not(:disabled) { background-color: var(--primary-color); color: var(--background-color); }
#submit-btn:disabled { border-color: #555; color: #555; cursor: not-allowed; }
#response-container { margin-top: 2rem; display: none; border-top: 1px solid var(--border-color); padding-top: 2rem; }
#response-title { color: var(--secondary-color); font-family: 'Orbitron'; font-size: 1.5rem; margin-bottom: 1rem; }
.result-card {
background: rgba(20, 20, 20, 0.7);
border: 1px solid #333;
margin-bottom: 1.5rem;
padding: 1.5rem;
}
.result-card h3 { color: var(--primary-color); border-bottom: 1px solid var(--border-color); padding-bottom: 0.5rem; margin-bottom: 1rem; }
.result-item { display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.8rem; flex-wrap: wrap; gap: 1rem; }
.result-item .label { font-weight: bold; }
.result-item .value { font-weight: bold; }
.value.high { color: #ff4d4d; } .value.medium, .value.moderate { color: #ffc107; } .value.low, .value.very, .value.identical { color: #4caf50; } .value.different { color: #ffc107;}
.confidence-bar-container { width: 100%; max-width: 200px; background: #333; height: 10px; border-radius: 5px; overflow: hidden; }
.confidence-bar { height: 100%; background: var(--secondary-color); border-radius: 5px; transition: width 0.5s ease; }
.result-details { border: 1px solid #222; margin-top: 1rem; }
.result-details summary { cursor: pointer; padding: 0.8rem; background-color: #1a1a1a; font-weight: bold; }
.result-details-content { padding: 1rem; background-color: #111; }
.detail-item { display: grid; grid-template-columns: 200px 1fr; gap: 0.5rem; margin-bottom: 0.5rem; word-break: break-all; }
.detail-item .key { color: var(--text-color); }
.detail-item .value { color: #b0b0b0; }
.detail-item img { max-width: 100%; border: 1px solid #444; margin-top: 0.5rem; }
</style>
</head>
<body>
<div class="container">
<header>
<h1>FALCONLENS</h1>
<p>ADVANCED IMAGE FORENSICS // FALCONS.AI</p>
</header>
<div class="mode-switcher">
<button class="tab-btn active" data-mode="single">Single Image Analysis</button>
<button class="tab-btn" data-mode="comparison">Comparison Analysis</button>
</div>
<!-- Single Image Form -->
<form id="single-form" class="analysis-form active">
<div class="upload-grid">
<label class="drop-zone" id="single-drop-zone">
<h3>SUSPECT IMAGE</h3>
<p>DRAG & DROP OR CLICK TO UPLOAD</p>
<p class="file-name" id="single-file-name"></p>
<input type="file" id="single-file-input" accept="image/*">
</label>
</div>
</form>
<!-- Comparison Form -->
<form id="comparison-form" class="analysis-form">
<div class="upload-grid two-cols">
<label class="drop-zone" id="original-drop-zone">
<h3>ORIGINAL IMAGE</h3>
<p>DRAG & DROP OR CLICK</p>
<p class="file-name" id="original-file-name"></p>
<input type="file" id="original-file-input" accept="image/*">
</label>
<label class="drop-zone" id="suspect-drop-zone">
<h3>SUSPECT IMAGE</h3>
<p>DRAG & DROP OR CLICK</p>
<p class="file-name" id="suspect-file-name"></p>
<input type="file" id="suspect-file-input" accept="image/*">
</label>
</div>
</form>
<button id="submit-btn" disabled>SELECT IMAGE(S) TO BEGIN</button>
<div id="response-container">
<h2 id="response-title"></h2>
<div id="response-content"></div>
</div>
</div>
<script>
let currentMode = 'single';
const tabs = document.querySelectorAll('.tab-btn');
const forms = document.querySelectorAll('.analysis-form');
const submitBtn = document.getElementById('submit-btn');
const responseContainer = document.getElementById('response-container');
const responseTitle = document.getElementById('response-title');
const responseContent = document.getElementById('response-content');
const singleFileInput = document.getElementById('single-file-input');
const originalFileInput = document.getElementById('original-file-input');
const suspectFileInput = document.getElementById('suspect-file-input');
function setupDropZone(dropZoneId, inputId, nameDisplayId) {
const dropZone = document.getElementById(dropZoneId);
const fileInput = document.getElementById(inputId);
const nameDisplay = document.getElementById(nameDisplayId);
dropZone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => handleFile(fileInput.files[0], nameDisplay, fileInput));
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(e => dropZone.addEventListener(e, preventDefaults));
['dragenter', 'dragover'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.add('drag-over')));
['dragleave', 'drop'].forEach(e => dropZone.addEventListener(e, () => dropZone.classList.remove('drag-over')));
dropZone.addEventListener('drop', (e) => handleFile(e.dataTransfer.files[0], nameDisplay, fileInput));
}
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function handleFile(file, nameDisplay, inputElement) {
if (file && file.type.startsWith('image/')) {
nameDisplay.textContent = `[ ${file.name} ]`;
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
inputElement.files = dataTransfer.files;
validateForm();
}
}
function validateForm() {
let isValid = false;
if (currentMode === 'single') {
if (singleFileInput.files.length > 0) {
isValid = true;
submitBtn.textContent = 'ANALYZE SINGLE IMAGE';
} else {
submitBtn.textContent = 'SELECT SUSPECT IMAGE';
}
}
else if (currentMode === 'comparison') {
const hasOriginal = originalFileInput.files.length > 0;
const hasSuspect = suspectFileInput.files.length > 0;
if (hasOriginal && hasSuspect) {
isValid = true;
submitBtn.textContent = 'ANALYZE COMPARISON';
} else if (hasOriginal && !hasSuspect) {
submitBtn.textContent = 'SELECT SUSPECT IMAGE';
} else if (!hasOriginal && hasSuspect) {
submitBtn.textContent = 'SELECT ORIGINAL IMAGE';
} else {
submitBtn.textContent = 'SELECT BOTH IMAGES';
}
}
submitBtn.disabled = !isValid;
}
tabs.forEach(tab => {
tab.addEventListener('click', () => {
currentMode = tab.dataset.mode;
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
forms.forEach(f => f.classList.remove('active'));
document.getElementById(`${currentMode}-form`).classList.add('active');
validateForm();
clearResponse();
});
});
setupDropZone('single-drop-zone', 'single-file-input', 'single-file-name');
setupDropZone('original-drop-zone', 'original-file-input', 'original-file-name');
setupDropZone('suspect-drop-zone', 'suspect-file-input', 'suspect-file-name');
// Handle form submission - REFACTORED FOR NEW API STRUCTURE
submitBtn.addEventListener('click', async () => {
// --- REFACTORED SECTION START ---
// IMPORTANT: Replace with your actual RapidAPI key.
// You can get a key from https://rapidapi.com/falcons-falcons-default/api/falconlens1
const API_KEY = "";
const API_HOST = "falconlens1.p.rapidapi.com";
let apiUrl = '';
const formData = new FormData();
if (currentMode === 'single') {
if (singleFileInput.files.length === 0) return;
formData.append('image', singleFileInput.files[0]);
apiUrl = 'https://falconlens1.p.rapidapi.com/forensics/analyze-single';
} else {
if (originalFileInput.files.length === 0 || suspectFileInput.files.length === 0) return;
formData.append('original_image', originalFileInput.files[0]);
formData.append('suspect_image', suspectFileInput.files[0]);
apiUrl = 'https://falconlens1.p.rapidapi.com/forensics/analyze-comparison';
}
const options = {
method: 'POST',
headers: {
'x-rapidapi-key': API_KEY,
'x-rapidapi-host': API_HOST
// 'Content-Type': 'multipart/form-data' is set automatically by the browser when using FormData
},
body: formData
};
setLoadingState(true);
try {
// Check if the user has replaced the placeholder API key
if (API_KEY === "YOUR_RAPIDAPI_KEY_HERE") {
throw new Error("API Key not set. Please replace 'YOUR_RAPIDAPI_KEY_HERE' in the script.");
}
const response = await fetch(apiUrl, options);
if (!response.ok) {
// Try to parse the JSON error response from the API
const errData = await response.json().catch(() => ({ detail: "Could not parse error response from server." }));
throw new Error(errData.detail || `HTTP Error: ${response.status}`);
}
const data = await response.json();
displayResults(data.results);
} catch (error) {
displayError(error.message);
} finally {
setLoadingState(false);
}
// --- REFACTORED SECTION END ---
});
function setLoadingState(isLoading) {
if (isLoading) {
submitBtn.disabled = true;
submitBtn.textContent = "ANALYZING...";
responseContainer.style.display = 'block';
responseTitle.textContent = "SYSTEM // PROCESSING REQUEST";
responseContent.innerHTML = `<p>Transmitting data to FALCONLENS core... Please wait.</p>`;
} else {
validateForm();
}
}
function displayResults(results) {
responseTitle.textContent = "SYSTEM // ANALYSIS COMPLETE";
responseContent.innerHTML = '';
if (results && results.length > 0) {
results.forEach(result => {
responseContent.innerHTML += createResultCard(result);
});
} else {
responseContent.innerHTML = `<p>Analysis complete, but no results were returned.</p>`;
}
}
function displayError(message) {
responseTitle.textContent = "SYSTEM // ERROR";
responseContent.innerHTML = `<p style="color:var(--primary-color);">Connection failed or an error occurred: <br/><strong>${message}</strong></p><p>Please check the browser console and ensure the FALCONLENS API server is running and accessible with a valid API Key.</p>`;
}
function clearResponse() {
responseContainer.style.display = 'none';
responseTitle.textContent = '';
responseContent.innerHTML = '';
}
function createResultCard(result) {
const likelihoodClass = result.manipulation_likelihood.toLowerCase().replace(/\s+/g, '-');
const confidence = result.confidence_score * 100;
let detailsHtml = '';
if (result.details) {
for (const [key, value] of Object.entries(result.details)) {
const cleanKey = key.replace(/_b64$/, '').replace(/_/g, ' ');
if (key.endsWith('_b64') && value) {
detailsHtml += `<div class="detail-item">
<span class="key" style="text-transform: capitalize;">${cleanKey}:</span>
<span class="value"><img src="data:image/jpeg;base64,${value}" alt="${cleanKey}"></span>
</div>`;
} else if (typeof value === 'object' && value !== null) {
detailsHtml += `<div class="detail-item">
<span class="key" style="text-transform: capitalize;">${cleanKey}:</span>
<span class="value">${JSON.stringify(value)}</span>
</div>`;
} else {
detailsHtml += `<div class="detail-item">
<span class="key" style="text-transform: capitalize;">${cleanKey}:</span>
<span class="value">${value}</span>
</div>`;
}
}
}
return `
<div class="result-card">
<h3>${result.test_name}</h3>
<div class="result-item">
<span class="label">Manipulation Likelihood:</span>
<span class="value ${likelihoodClass}">${result.manipulation_likelihood}</span>
</div>
<div class="result-item">
<span class="label">Confidence Score (${confidence.toFixed(1)}%):</span>
<div class="confidence-bar-container">
<div class="confidence-bar" style="width: ${confidence}%;"></div>
</div>
</div>
<details class="result-details">
<summary>VIEW TECHNICAL DETAILS</summary>
<div class="result-details-content">${detailsHtml || '<p>No technical details available for this test.</p>'}</div>
</details>
</div>
`;
}
// Initial validation check on page load
validateForm();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment