Chroma-Forge-AI / index.html
Elliottes's picture
Update index.html
ba3fd12 verified
Raw
History Blame Contribute Delete
12 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chroma-Forge AI | Material Synthesizer</title>
<style>
:root {
--bg: #090a0f;
--panel: #12141d;
--accent: #ff3366;
--text: #e2e8f0;
--glow: rgba(255, 51, 102, 0.4);
}
* { box-sizing: border-box; margin: 0; padding: 0; font-family: 'Courier New', Courier, monospace; }
body { background-color: var(--bg); color: var(--text); min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; }
header { width: 100%; max-width: 1000px; display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 20px; border-bottom: 1px solid #333; padding-bottom: 15px; }
h1 { color: var(--accent); font-size: 24px; text-transform: uppercase; letter-spacing: 2px; text-shadow: 0 0 10px var(--glow); }
.api-config { display: flex; gap: 10px; align-items: center; }
.api-config input { background: #000; border: 1px solid #444; color: #fff; padding: 6px 10px; border-radius: 4px; font-size: 12px; width: 200px; outline: none;}
.api-config input:focus { border-color: var(--accent); }
.desk { width: 100%; max-width: 1000px; display: grid; grid-template-columns: 1fr 3fr 1fr; gap: 20px; background: var(--panel); padding: 30px; border-radius: 12px; border: 1px solid #222; box-shadow: 0 10px 50px rgba(0,0,0,0.8); }
.fader-bank { display: flex; flex-direction: column; justify-content: space-between; gap: 20px; }
.slider-wrapper { text-align: center; background: #0a0b10; padding: 15px 10px; border-radius: 8px; border: 1px solid #1a1c23; }
.slider-label { font-size: 11px; color: #888; font-weight: bold; letter-spacing: 1px; margin-bottom: 15px; display: flex; justify-content: space-between; }
.slider-label span.active { color: var(--text); text-shadow: 0 0 5px #fff; }
input[type="range"] { -webkit-appearance: none; width: 100%; height: 4px; background: #333; border-radius: 2px; outline: none; }
input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 24px; background: #555; border: 1px solid #888; border-radius: 2px; cursor: pointer; transition: 0.2s; }
input[type="range"]::-webkit-slider-thumb:hover { background: var(--accent); border-color: #fff; box-shadow: 0 0 10px var(--glow); }
.viewport { position: relative; width: 100%; aspect-ratio: 1/1; background: #000; border-radius: 8px; border: 2px solid #333; overflow: hidden; display: flex; justify-content: center; align-items: center; }
.viewport img { width: 100%; height: 100%; object-fit: cover; display: none; }
/* The Scanning Laser Loading Animation */
.scanner { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: var(--accent); box-shadow: 0 0 15px var(--accent); animation: scan 2s linear infinite; display: none; }
@keyframes scan { 0% { top: 0; opacity: 0;} 10% { opacity: 1; } 90% { opacity: 1; } 100% { top: 100%; opacity: 0;} }
.status-text { color: #555; font-size: 14px; letter-spacing: 2px; }
.controls { grid-column: 1 / -1; display: flex; justify-content: space-between; align-items: center; margin-top: 10px; border-top: 1px solid #222; padding-top: 20px; }
.prompt-readout { flex: 1; background: #000; padding: 12px; font-size: 11px; color: #666; border-radius: 4px; border: 1px solid #222; margin-right: 20px; line-height: 1.5; }
.btn-synth { background: transparent; border: 1px solid var(--accent); color: var(--accent); padding: 15px 30px; font-size: 16px; font-weight: bold; letter-spacing: 2px; cursor: pointer; border-radius: 4px; transition: all 0.3s; text-transform: uppercase; }
.btn-synth:hover { background: var(--glow); box-shadow: 0 0 20px var(--glow); color: #fff; }
.btn-synth:disabled { border-color: #444; color: #444; cursor: not-allowed; box-shadow: none; background: transparent; }
</style>
</head>
<body>
<header>
<div>
<h1>Chroma-Forge AI</h1>
<p style="color: #666; font-size: 12px; margin-top: 5px;">Latent Material Texture Synthesizer v2.0</p>
</div>
<div class="api-config">
<span style="font-size: 11px; color: #888;">HF TOKEN:</span>
<input type="password" id="apiKey" placeholder="hf_xxxxxxxxxxxxxxxxxxx" />
</div>
</header>
<div class="desk">
<!-- Left Fader Bank -->
<div class="fader-bank">
<div class="slider-wrapper">
<div class="slider-label">
<span id="lbl-org">ORGANIC</span>
<span id="lbl-syn">SYNTHETIC</span>
</div>
<input type="range" id="sl-nature" min="0" max="100" value="50">
</div>
<div class="slider-wrapper">
<div class="slider-label">
<span id="lbl-smo">SMOOTH</span>
<span id="lbl-jag">JAGGED</span>
</div>
<input type="range" id="sl-surface" min="0" max="100" value="50">
</div>
</div>
<!-- Center Viewport -->
<div class="viewport" id="viewportBox">
<span class="status-text" id="statusText">AWAITING SYNTHESIS</span>
<img id="materialOutput" alt="Generated Material" />
<div class="scanner" id="scanner"></div>
</div>
<!-- Right Fader Bank -->
<div class="fader-bank">
<div class="slider-wrapper">
<div class="slider-label">
<span id="lbl-ari">ARID</span>
<span id="lbl-wet">WET</span>
</div>
<input type="range" id="sl-moisture" min="0" max="100" value="50">
</div>
<div class="slider-wrapper">
<div class="slider-label">
<span id="lbl-ord">ORDER</span>
<span id="lbl-cha">CHAOS</span>
</div>
<input type="range" id="sl-structure" min="0" max="100" value="50">
</div>
</div>
<!-- Bottom Controls -->
<div class="controls">
<div class="prompt-readout" id="promptDisplay">Raw compiler translation will appear here...</div>
<button class="btn-synth" id="btnSynth">Synthesize</button>
</div>
</div>
<script>
// DOM Elements
const apiKeyInput = document.getElementById('apiKey');
const btnSynth = document.getElementById('btnSynth');
const promptDisplay = document.getElementById('promptDisplay');
const materialOutput = document.getElementById('materialOutput');
const scanner = document.getElementById('scanner');
const statusText = document.getElementById('statusText');
const viewportBox = document.getElementById('viewportBox');
// Sliders
const sNature = document.getElementById('sl-nature');
const sSurface = document.getElementById('sl-surface');
const sMoist = document.getElementById('sl-moisture');
const sStruct = document.getElementById('sl-structure');
// Load API Key from local storage if exists
if(localStorage.getItem('hf_token')) {
apiKeyInput.value = localStorage.getItem('hf_token');
}
apiKeyInput.addEventListener('change', () => {
localStorage.setItem('hf_token', apiKeyInput.value.trim());
});
// The Alchemist Engine: Turns slider math into AI Prompts
function buildPrompt() {
let n = parseInt(sNature.value);
let s = parseInt(sSurface.value);
let m = parseInt(sMoist.value);
let st = parseInt(sStruct.value);
// Interpolate keywords based on threshold values
let natureTerm = n < 40 ? "biological tissue, flesh, alien flora, organic matter" :
n > 60 ? "metallic, synthetic, hard-surface, cybernetic plating" :
"biomechanical hybrid, techno-organic material";
let surfaceTerm = s < 40 ? "smooth, highly polished, glossy, flowing" :
s > 60 ? "jagged, sharp, rough, crystalline, shattered" :
"textured surface with subtle ridges";
let moistTerm = m < 40 ? "dry, cracked, arid, dusty, matte" :
m > 60 ? "wet, slimy, dripping, moist, specular highlights" :
"damp surface";
let structTerm = st < 40 ? "geometric, grid repeating pattern, highly ordered" :
st > 60 ? "chaotic, tangled, asymmetric, distorted, noisy" :
"semi-structured pattern";
// Final Prompt Construction
const finalPrompt = `A seamless texture map of ${natureTerm}, ${surfaceTerm}, ${moistTerm}, ${structTerm}. Overhead flat view, high resolution, Unreal Engine 5 render, PBR material, highly detailed procedural texture.`;
promptDisplay.innerText = finalPrompt;
return finalPrompt;
}
// Live update the prompt readout when dragging sliders
[sNature, sSurface, sMoist, sStruct].forEach(slider => {
slider.addEventListener('input', buildPrompt);
});
// Initial setup
buildPrompt();
// The AI Synthesis Function
async function synthesize() {
const token = apiKeyInput.value.trim();
if (!token) {
alert("Please enter your Hugging Face API Token in the top right.");
return;
}
const prompt = buildPrompt();
// UI State: Loading
btnSynth.disabled = true;
btnSynth.innerText = "PROCESSING...";
viewportBox.style.borderColor = "var(--accent)";
statusText.style.display = "block";
statusText.innerText = "CALLING LATENT INFERENCE...";
materialOutput.style.display = "none";
scanner.style.display = "block";
try {
// Calling the lightning-fast FLUX.1 model on Hugging Face API
const response = await fetch(
"https://api-inference.huggingface.co/models/black-forest-labs/FLUX.1-schnell",
{
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({ inputs: prompt }),
}
);
if (!response.ok) {
throw new Error(`API Error: ${response.status} - Check if your token is valid.`);
}
const blob = await response.blob();
const imageUrl = URL.createObjectURL(blob);
// UI State: Success
materialOutput.src = imageUrl;
materialOutput.style.display = "block";
statusText.style.display = "none";
} catch (error) {
statusText.innerText = "SYNTHESIS FAILED";
alert(error.message);
} finally {
// UI State: Reset
scanner.style.display = "none";
viewportBox.style.borderColor = "#333";
btnSynth.disabled = false;
btnSynth.innerText = "SYNTHESIZE";
}
}
btnSynth.addEventListener('click', synthesize);
</script>
</body>
</html>