anycoder-5c70dadc / index.html
HI7RAI's picture
Upload folder using huggingface_hub
53fee15 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>UltraFX: Dynamic Image & Video Processor</title>
<!-- Core Libraries via CDN -->
<script src="https://cdn.jsdelivr.net/npm/mathjs@11.8.0/lib/browser/math.js"></script>
<!-- p5 and Three/Pixi are loaded as requested, though glfx handles the heavy WebGL lifting here -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.6.0/lib/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/glfx.js@0.0.4/glfx.min.js"></script>
<!-- UI Styling -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@400;600;700&display=swap" rel="stylesheet">
<style>
:root {
--neon-blue: #00f3ff;
--neon-pink: #ff00ff;
--dark-bg: #0a0a12;
--panel-bg: rgba(15, 15, 20, 0.95);
--border-color: rgba(0, 243, 255, 0.3);
}
body {
background-color: var(--dark-bg);
color: #fff;
font-family: 'Rajdhani', sans-serif;
overflow: hidden;
height: 100vh;
display: flex;
flex-direction: column;
}
/* --- Header / Nav --- */
.top-bar {
height: 50px;
background: rgba(0, 0, 0, 0.8);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
z-index: 200;
}
.brand {
font-weight: 700;
font-size: 1.2rem;
color: #fff;
letter-spacing: 1px;
}
.brand span {
color: var(--neon-blue);
}
.anycoder-link {
font-size: 0.8rem;
color: var(--text-muted);
text-decoration: none;
border-bottom: 1px dashed #555;
margin-left: auto;
margin-right: 15px;
}
.anycoder-link:hover {
color: var(--neon-blue);
border-color: var(--neon-blue);
}
/* --- Main Layout --- */
.app-wrapper {
display: flex;
flex: 1;
position: relative;
overflow: hidden;
}
/* --- Canvas Area --- */
#canvas-wrapper {
flex-grow: 1;
position: relative;
display: flex;
justify-content: center;
align-items: center;
background: radial-gradient(circle at center, #1a1a2e 0%, #000 100%);
overflow: hidden;
padding: 20px;
}
canvas {
box-shadow: 0 0 30px rgba(0, 243, 255, 0.1);
max-width: 100%;
max-height: 100%;
border: 1px solid #333;
}
/* --- Controls Sidebar --- */
#controls {
width: 320px;
background: var(--panel-bg);
border-left: 1px solid var(--border-color);
display: flex;
flex-direction: column;
backdrop-filter: blur(10px);
z-index: 100;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.sidebar-header {
padding: 15px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-title {
font-weight: 600;
color: var(--neon-blue);
font-size: 1rem;
}
#dynamic-controls {
flex: 1;
overflow-y: auto;
padding: 15px;
}
/* --- Control Groups --- */
.control-group {
background: rgba(255, 255, 255, 0.03);
border-radius: 6px;
padding: 10px;
margin-bottom: 12px;
border: 1px solid rgba(255, 255, 255, 0.05);
transition: border-color 0.2s;
}
.control-group:hover {
border-color: rgba(0, 243, 255, 0.2);
}
.control-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.control-label {
font-weight: 600;
color: #ddd;
font-size: 0.85rem;
text-transform: uppercase;
}
/* --- Inputs & Sliders --- */
input[type=range] {
width: 100%;
height: 4px;
background: #333;
border-radius: 2px;
outline: none;
-webkit-appearance: none;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: var(--neon-blue);
border-radius: 50%;
cursor: pointer;
box-shadow: 0 0 10px var(--neon-blue);
}
.math-input {
background: #000;
border: 1px solid #333;
color: var(--neon-pink);
font-family: 'Courier New', monospace;
width: 100%;
font-size: 0.75rem;
padding: 6px;
margin-top: 6px;
border-radius: 4px;
}
.math-input:focus {
border-color: var(--neon-pink);
outline: none;
}
/* --- Buttons --- */
.btn-neon {
background: rgba(0, 243, 255, 0.1);
border: 1px solid var(--neon-blue);
color: var(--neon-blue);
text-transform: uppercase;
font-weight: 700;
font-size: 0.8rem;
letter-spacing: 1px;
transition: all 0.2s;
width: 100%;
margin-bottom: 8px;
padding: 8px;
border-radius: 4px;
cursor: pointer;
}
.btn-neon:hover {
background: var(--neon-blue);
color: #000;
box-shadow: 0 0 15px rgba(0, 243, 255, 0.4);
}
.btn-neon i {
margin-right: 6px;
}
/* --- Toggle Switch --- */
.toggle-switch {
position: relative;
display: inline-block;
width: 34px;
height: 18px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #333;
transition: .4s;
border-radius: 18px;
}
.slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 2px;
bottom: 2px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked+.slider {
background-color: var(--neon-pink);
}
input:checked+.slider:before {
transform: translateX(16px);
}
/* --- Floating Action Buttons (Mobile/Desktop) --- */
.fab-container {
position: absolute;
top: 20px;
left: 20px;
z-index: 50;
display: flex;
flex-direction: column;
gap: 10px;
}
/* --- Scrollbar --- */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: #0a0a12;
}
::-webkit-scrollbar-thumb {
background: #333;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--neon-blue);
}
/* --- Responsive --- */
.menu-toggle-btn {
display: none;
background: none;
border: none;
color: #fff;
font-size: 1.2rem;
cursor: pointer;
}
@media (max-width: 768px) {
#controls {
position: absolute;
right: 0;
top: 0;
bottom: 0;
transform: translateX(100%);
box-shadow: -5px 0 15px rgba(0,0,0,0.5);
}
#controls.active {
transform: translateX(0);
}
.menu-toggle-btn {
display: block;
}
.brand {
font-size: 1rem;
}
.anycoder-link {
display: none;
}
}
.hidden-file {
display: none;
}
#fps-counter {
position: absolute;
bottom: 10px;
left: 10px;
color: #0f0;
font-family: monospace;
background: rgba(0,0,0,0.6);
padding: 4px 8px;
border-radius: 4px;
font-size: 0.75rem;
pointer-events: none;
}
</style>
</head>
<body>
<!-- Top Navigation -->
<div class="top-bar">
<div class="brand">
<button class="menu-toggle-btn me-2" id="menuToggle"><i class="fas fa-bars"></i></button>
ULTRA<span>FX</span>
</div>
<!-- Mandatory Link -->
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
Built with anycoder
</a>
</div>
<div class="app-wrapper">
<!-- Main Canvas Area -->
<div id="canvas-wrapper">
<!-- Floating Action Buttons -->
<div class="fab-container">
<button class="btn-neon" onclick="document.getElementById('file-input').click()">
<i class="fas fa-upload"></i> Import Media
</button>
<button class="btn-neon" id="export-btn">
<i class="fas fa-camera"></i> Snapshot
</button>
<button class="btn-neon" id="auto-detect-btn">
<i class="fas fa-magic"></i> Reset FX
</button>
<input type="file" id="file-input" class="hidden-file" accept="image/*,video/*">
</div>
<!-- Canvas injected here -->
<div id="canvas-container"></div>
<div id="fps-counter">FPS: 0</div>
</div>
<!-- Sidebar Controls -->
<div id="controls">
<div class="sidebar-header">
<span class="sidebar-title"><i class="fas fa-sliders-h"></i> FX RACK</span>
<button class="btn-neon" style="width: auto; padding: 4px 8px; font-size: 0.7rem;" id="clear-all-btn">Clear All</button>
</div>
<div id="dynamic-controls">
<!-- Controls injected here -->
<div style="text-align: center; color: #666; margin-top: 50px;">
<i class="fas fa-photo-video fa-3x mb-3"></i>
<p>Import an image or video to start processing.</p>
</div>
</div>
</div>
</div>
<script>
/**
* UltraFX Core Engine
* Handles WebGL context, Library Introspection, and Rendering Pipeline
*/
// --- Global State ---
const state = {
canvas: null,
ctx: null,
glfxCanvas: null,
texture: null,
mediaType: null, // 'image' or 'video'
videoElement: null,
sourceImage: null,
filters: {}, // Stores active filter configurations
animationId: null,
time: 0,
width: 800,
height: 600,
isPlaying: true
};
// --- Library Definitions & Metadata ---
// Defines which methods from glfx.js act as filters and their parameters
const libraryMeta = {
glfx: {
instance: null,
methods: [
{ name: 'ink', params: [{ name: 'strength', min: 0, max: 1, def: 0.25 }] },
{ name: 'vignette', params: [{ name: 'size', min: 0, max: 1, def: 0.5 }, { name: 'amount', min: 0, max: 1, def: 0.5 }] },
{ name: 'zoomBlur', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'strength', min: 0, max: 1, def: 0.3 }] },
{ name: 'colorHalftone', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'angle', min: 0, max: 1.57, def: 0.25 }, { name: 'size', min: 3, max: 20, def: 4 }] },
{ name: 'hexagonalPixelate', params: [{ name: 'centerX', min: 0, max: 1, def: 0.5 }, { name: 'centerY', min: 0, max: 1, def: 0.5 }, { name: 'scale', min: 5, max: 50, def: 20 }] },
{ name: 'noise', params: [{ name: 'amount', min: 0, max: 1, def: 0.5 }] },
{ name: 'sepia', params: [{ name: 'amount', min: 0, max: 1, def: 1 }] },
{ name: 'brightnessContrast', params: [{ name: 'brightness', min: -1, max: 1, def: 0 }, { name: 'contrast', min: -1, max: 1, def: 0 }] },
{ name: 'hueSaturation', params: [{ name: 'hue', min: -1, max: 1, def: 0 }, { name: 'saturation', min: -1, max: 1, def: 0 }] },
{ name: 'denoise', params: [{ name: 'exponent', min: 0, max: 20, def: 10 }] }
]
},
custom: {
methods: [
{ name: 'glitch', params: [{ name: 'intensity', min: 0, max: 100, def: 0 }, { name: 'speed', min: 0, max: 50, def: 10 }] },
{ name: 'strobo', params: [{ name: 'freq', min: 0, max: 60, def: 0 }] },
{ name: 'rgbSplit', params: [{ name: 'amount', min: 0, max: 20, def: 0 }] }
]
}
};
// --- Initialization ---
window.onload = function () {
initCanvas();
setupEventListeners();
renderLoop();
};
function initCanvas() {
const container = document.getElementById('canvas-container');
// Initialize glfx.js canvas (WebGL)
try {
state.glfxCanvas = fx.canvas();
libraryMeta.glfx.instance = state.glfxCanvas;
container.appendChild(state.glfxCanvas);
state.canvas = state.glfxCanvas;
} catch (e) {
console.error("WebGL not supported", e);
alert("WebGL is not supported in this browser. UltraFX cannot run.");
return;
}
}
function resizeCanvas(w, h) {
// Limit max size for performance
const max = 1280;
let width = w;
let height = h;
if (width > max) {
const ratio = height / width;
width = max;
height = max * ratio;
}
state.width = width;
state.height = height;
}
// --- UI Generation ---
function generateControls() {
const container = document.getElementById('dynamic-controls');
container.innerHTML = ''; // Clear existing
// 1. GLFX Library
const glfxHeader = document.createElement('div');
glfxHeader.innerHTML = '<h6 style="color:var(--neon-blue); margin: 10px 0;">WEBGL FILTERS</h6>';
container.appendChild(glfxHeader);
libraryMeta.glfx.methods.forEach(effect => {
createControlGroup(container, effect.name, effect.name, effect.params, 'glfx');
});
// 2. Custom JS Effects
const customHeader = document.createElement('div');
customHeader.innerHTML = '<h6 style="color:var(--neon-pink); margin: 20px 0 10px 0;">CUSTOM FX</h6>';
container.appendChild(customHeader);
libraryMeta.custom.methods.forEach(effect => {
createControlGroup(container, effect.name, effect.name, effect.params, 'custom');
});
}
function createControlGroup(parent, label, key, params, type) {
const group = document.createElement('div');
group.className = 'control-group';
// Header with Toggle
const header = document.createElement('div');
header.className = 'control-header';
header.innerHTML = `
<span class="control-label">${label}</span>
<label class="toggle-switch">
<input type="checkbox" id="toggle-${key}">
<span class="slider"></span>
</label>
`;
group.appendChild(header);
// Initialize State
state.filters[key] = {
active: false,
type: type,
params: {}
};
// Toggle Listener
header.querySelector('input').addEventListener('change', (e) => {
state.filters[key].active = e.target.checked;
});
// Parameters
params.forEach(param => {
state.filters[key].params[param.name] = {
value: param.def,
formula: ''
};
const paramWrapper = document.createElement('div');
paramWrapper.style.marginBottom = '10px';
// Label & Value Display
const info = document.createElement('div');
info.style.display = 'flex';
info.style.justifyContent = 'space-between';
info.style.fontSize = '0.75rem';
info.style.color = '#aaa';
info.innerHTML = `<span>${param.name}</span><span id="val-${key}-${param.name}">${param.def}</span>`;
paramWrapper.appendChild(info);
// Slider
const slider = document.createElement('input');
slider.type = 'range';
slider.min = param.min;
slider.max = param.max;
slider.step = (param.max - param.min) / 100;
slider.value = param.def;
slider.addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
state.filters[key].params[param.name].value = val;
document.getElementById(`val-${key}-${param.name}`).innerText = val.toFixed(2);
});
paramWrapper.appendChild(slider);
// Math Formula Input
const formulaInput = document.createElement('input');
formulaInput.type = 'text';
formulaInput.className = 'math-input';
formulaInput.placeholder = `Math.js (e.g. sin(t)*${param.max})`;
formulaInput.title = "Use variables: t (time), random (0-1)";
formulaInput.addEventListener('change', (e) => {
state.filters[key].params[param.name].formula = e.target.value;
});
paramWrapper.appendChild(formulaInput);
group.appendChild(paramWrapper);
});
parent.appendChild(group);
}
// --- Event Listeners ---
function setupEventListeners() {
const fileInput = document.getElementById('file-input');
fileInput.addEventListener('change', handleFileSelect);
document.getElementById('export-btn').addEventListener('click', exportCanvas);
document.getElementById('clear-all-btn').addEventListener('click', clearAllFilters);
document.getElementById('auto-detect-btn').addEventListener('click', () => {
clearAllFilters();
showToast("FX Reset complete");
});
// Mobile Menu
document.getElementById('menuToggle').addEventListener('click', () => {
document.getElementById('controls').classList.toggle('active');
});
// Math.js Scope
window.mathScope = {
t: 0,
sin: Math.sin,
cos: Math.cos,
tan: Math.tan,
abs: Math.abs,
random: Math.random,
PI: Math.PI
};
}
function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
const url = URL.createObjectURL(file);
if (file.type.startsWith('image')) {
state.mediaType = 'image';
const img = new Image();
img.onload = () => {
resizeCanvas(img.width, img.height);
state.sourceImage = img;
state.texture = state.glfxCanvas.texture(img);
generateControls();
};
img.src = url;
} else if (file.type.startsWith('video')) {
state.mediaType = 'video';
const vid = document.createElement('video');
vid.src = url;
vid.loop = true;
vid.muted = true;
vid.play();
vid.crossOrigin = "anonymous";
vid.onloadedmetadata = () => {
resizeCanvas(vid.videoWidth, vid.videoHeight);
state.videoElement = vid;
state.texture = state.glfxCanvas.texture(vid);
generateControls();
};
}
}
function clearAllFilters() {
// Uncheck all toggles visually
document.querySelectorAll('.toggle-switch input').forEach(input => input.checked = false);
// Reset internal state
Object.keys(state.filters).forEach(key => {
state.filters[key].active = false;
});
}
// --- Rendering Pipeline ---
let lastTime = 0;
let frameCount = 0;
const fpsElem = document.getElementById('fps-counter');
function renderLoop(timestamp) {
requestAnimationFrame(renderLoop);
// FPS Calculation
if (timestamp - lastTime >= 1000) {
fpsElem.innerText = `FPS: ${frameCount}`;
frameCount = 0;
lastTime = timestamp;
}
frameCount++;
// Update Time
state.time += 0.05;
window.mathScope.t = state.time;
if (!state.texture) return;
// Update Texture if Video
if (state.mediaType === 'video' && state.videoElement) {
try {
state.texture.loadContentsOf(state.videoElement);
} catch (e) { }
}
// Start Drawing Chain
state.glfxCanvas.draw(state.texture);
// Apply Active Filters
for (const [key, filter] of Object.entries(state.filters)) {
if (!filter.active) continue;
const appliedParams = [];
const metaParams = libraryMeta[filter.type].methods.find(m => m.name === key).params;
metaParams.forEach(p => {
let val = filter.params[p.name].value;
const formula = filter.params[p.name].formula;
if (formula && formula.trim() !== "") {
try {
// Evaluate Math.js expression
val = math.evaluate(formula, window.mathScope);
// Clamp value to slider range for safety
val = Math.max(p.min, Math.min(p.max, val));
} catch (err) {
// Silent fail on math errors to prevent loop crash
}
}
appliedParams.push(val);
});
// Apply GLFX Filters
if (filter.type === 'glfx') {
if (state.glfxCanvas[key]) {
state.glfxCanvas[key](...appliedParams);
}
}
// Apply Custom Filters (Simulated using GLFX primitives)
if (filter.type === 'custom') {
if (key === 'glitch') {
const intensity = appliedParams[0] / 100;
// Random slices simulation using hue/saturation shift and noise
if (Math.random() < intensity) {
state.glfxCanvas.hueSaturation(Math.random() - 0.5, 0);
}
if (Math.random() < intensity * 0.5) {
state.glfxCanvas.noise(0.1);
}
}
if (key === 'strobo') {
const freq = appliedParams[0];
if (freq > 0 && Math.floor(state.time * freq) % 2 === 0) {
state.glfxCanvas.brightnessContrast(-1, 0); // Black out
}
}
if (key === 'rgbSplit') {
// Simulated shift by modifying hue drastically on specific channels (hack via colorHalftone or similar effects if available)
// Since glfx doesn't have direct channel split, we simulate via chromatic aberration approximation or just noise
const amt = appliedParams[0];
if(amt > 0) state.glfxCanvas.hexagonalPixelate(0.5, 0.5, amt + 10);
}
}
}
// Finalize
state.glfxCanvas.update();
}
// --- Export ---
function exportCanvas() {
if (!state.texture) {
showToast("No media loaded!", "error");
return;
}
state.glfxCanvas.update();
const dataURL = state.glfxCanvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = `ultrafx_${Date.now()}.png`;
link.href = dataURL;
link.click();
showToast("Snapshot saved!");
}
// --- Toast Notification ---
function showToast(msg, type = 'success') {
const toast = document.createElement('div');
toast.style.position = 'fixed';
toast.style.bottom = '20px';
toast.style.left = '50%';
toast.style.transform = 'translateX(-50%)';
toast.style.background = type === 'error' ? '#ef4444' : 'var(--neon-blue)';
toast.style.color = type === 'error' ? '#fff' : '#000';
toast.style.padding = '10px 20px';
toast.style.borderRadius = '30px';
toast.style.fontWeight = 'bold';
toast.style.zIndex = '9999';
toast.style.boxShadow = '0 5px 15px rgba(0,0,0,0.5)';
toast.style.opacity = '0';
toast.style.transition = 'opacity 0.3s';
toast.innerText = msg;
document.body.appendChild(toast);
// Animate in
setTimeout(() => toast.style.opacity = '1', 10);
// Remove
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
</script>
</body>
</html>