anycoder-9483c3d7 / index.html
HI7RAI's picture
Upload folder using huggingface_hub
6b94f33 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cinematic Color Calibration & Light FX</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
:root {
--primary-color: #00f2ff;
--bg-color: #0a0a0a;
--panel-bg: rgba(15, 15, 20, 0.75);
--text-color: #e0e0e0;
--accent-glow: 0 0 10px rgba(0, 242, 255, 0.5);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: #000;
overflow: hidden;
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: var(--text-color);
}
/* Canvas */
#stage {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
/* Header */
header {
position: fixed;
top: 20px;
left: 20px;
z-index: 100;
pointer-events: none;
text-transform: uppercase;
letter-spacing: 2px;
font-size: 0.8rem;
opacity: 0.7;
}
header a {
color: var(--primary-color);
text-decoration: none;
font-weight: bold;
text-shadow: 0 0 5px var(--primary-color);
pointer-events: auto;
transition: all 0.3s ease;
}
header a:hover {
text-shadow: 0 0 15px var(--primary-color);
letter-spacing: 3px;
}
/* Control Panel */
.controls-container {
position: fixed;
right: 20px;
top: 50%;
transform: translateY(-50%);
width: 300px;
background: var(--panel-bg);
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px);
padding: 25px;
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
z-index: 100;
transition: opacity 0.3s;
}
.controls-container:hover {
opacity: 1;
}
h2 {
font-size: 1.1rem;
margin-bottom: 20px;
color: var(--primary-color);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.control-group {
margin-bottom: 18px;
}
.label-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
font-size: 0.85rem;
font-weight: 500;
}
.value-display {
color: var(--primary-color);
font-family: 'Courier New', monospace;
}
/* Custom Range Slider */
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
input[type=range]::-webkit-slider-thumb {
height: 16px;
width: 16px;
border-radius: 50%;
background: #fff;
cursor: pointer;
-webkit-appearance: none;
margin-top: -6px;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
transition: transform 0.1s;
}
input[type=range]::-webkit-slider-thumb:hover {
transform: scale(1.2);
background: var(--primary-color);
}
/* Specific Styles for Firefox */
input[type=range]::-moz-range-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
input[type=range]::-moz-range-thumb {
height: 16px;
width: 16px;
border: none;
border-radius: 50%;
background: #fff;
cursor: pointer;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
/* Section Toggles */
.section-title {
font-size: 0.7rem;
text-transform: uppercase;
color: #666;
margin-top: 20px;
margin-bottom: 10px;
letter-spacing: 1.5px;
}
.btn-group {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.toggle-btn {
flex: 1;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
color: #aaa;
padding: 8px;
border-radius: 4px;
cursor: pointer;
font-size: 0.8rem;
transition: all 0.2s;
}
.toggle-btn.active {
background: rgba(0, 242, 255, 0.1);
border-color: var(--primary-color);
color: var(--primary-color);
}
/* Responsive */
@media (max-width: 768px) {
.controls-container {
width: calc(100% - 40px);
top: auto;
bottom: 20px;
transform: none;
right: 20px;
left: 20px;
max-height: 40vh;
overflow-y: auto;
}
}
</style>
</head>
<body>
<canvas id="stage"></canvas>
<header>
<div>Video FX Engine</div>
<div style="margin-top: 5px; font-size: 0.7rem;">Interactive Light & Particle Simulation</div>
<div style="margin-top: 15px;">Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a></div>
</header>
<div class="controls-container">
<h2><i class="fas fa-sliders-h"></i> Calibration Deck</h2>
<div class="section-title">Color Grading</div>
<div class="control-group">
<div class="label-row">
<span>Exposure (Dark)</span>
<span id="val-exposure" class="value-display">100%</span>
</div>
<input type="range" id="exposure" min="0" max="200" value="100">
</div>
<div class="control-group">
<div class="label-row">
<span>Contrast</span>
<span id="val-contrast" class="value-display">100%</span>
</div>
<input type="range" id="contrast" min="0" max="200" value="100">
</div>
<div class="control-group">
<div class="label-row">
<span>Saturation</span>
<span id="val-saturation" class="value-display">100%</span>
</div>
<input type="range" id="saturation" min="0" max="200" value="100">
</div>
<div class="section-title">Atmosphere & FX</div>
<div class="control-group">
<div class="label-row">
<span>Light Intensity (Max)</span>
<span id="val-intensity" class="value-display">50%</span>
</div>
<input type="range" id="intensity" min="0" max="100" value="50">
</div>
<div class="control-group">
<div class="label-row">
<span>Particles (Dust)</span>
<span id="val-particles" class="value-display">500</span>
</div>
<input type="range" id="particles" min="0" max="1500" value="500">
</div>
<div class="control-group">
<div class="label-row">
<span>Ray Length</span>
<span id="val-rays" class="value-display">Auto</span>
</div>
<input type="range" id="rays" min="0" max="100" value="60">
</div>
<div class="btn-group">
<button class="toggle-btn active" id="btn-dust">Dust</button>
<button class="toggle-btn active" id="btn-rays">Light Rays</button>
<button class="toggle-btn" id="btn-animate">Auto Rotate</button>
</div>
<div style="font-size: 0.7rem; color: #666; margin-top: 10px; text-align: center;">
Move mouse to control light source
</div>
</div>
<script>
/**
* Cinematic Video Calibration & Particle Engine
* Simulates color grading, volumetric lighting (raytracing fake),
* and floating dust particles.
*/
const canvas = document.getElementById('stage');
const ctx = canvas.getContext('2d');
// State Management
const state = {
width: window.innerWidth,
height: window.innerHeight,
mouseX: window.innerWidth / 2,
mouseY: window.innerHeight / 3,
// Grading
exposure: 100,
contrast: 100,
saturation: 100,
// FX
intensity: 0.5,
rayLength: 0.6,
particleCount: 500,
// Toggles
showDust: true,
showRays: true,
autoRotate: false,
time: 0
};
// Resize Handler
function resize() {
state.width = window.innerWidth;
state.height = window.innerHeight;
canvas.width = state.width;
canvas.height = state.height;
}
window.addEventListener('resize', resize);
resize();
// Mouse Tracker
window.addEventListener('mousemove', (e) => {
state.mouseX = e.clientX;
state.mouseY = e.clientY;
});
// Touch Tracker
window.addEventListener('touchmove', (e) => {
state.mouseX = e.touches[0].clientX;
state.mouseY = e.touches[0].clientY;
});
// --- Particle System ---
class Particle {
constructor() {
this.reset(true);
}
reset(randomY = false) {
this.x = Math.random() * state.width;
this.y = randomY ? Math.random() * state.height : state.height + 10;
this.z = Math.random() * 2 + 0.5; // Depth simulation
this.size = Math.random() * 2;
this.speedY = (Math.random() * 0.5 + 0.2) * this.z;
this.speedX = (Math.random() - 0.5) * 0.2;
this.opacity = Math.random() * 0.5 + 0.1;
}
update() {
this.y -= this.speedY;
this.x += Math.sin(state.time * 0.05 + this.y * 0.01) * 0.2; // Wavy motion
// Mouse repulsion/attraction
const dx = this.x - state.mouseX;
const dy = this.y - state.mouseY;
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist < 200) {
this.x += dx * 0.01;
this.y += dy * 0.01;
}
if (this.y < -10) {
this.reset();
}
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * this.z, 0, Math.PI * 2);
ctx.fillStyle = `rgba(220, 220, 200, ${this.opacity * state.intensity})`;
ctx.fill();
}
}
let particles = [];
function initParticles() {
particles = [];
for (let i = 0; i < 2000; i++) { // Max pool
particles.push(new Particle());
}
}
initParticles();
// --- Volumetric Light Rays (God Rays) ---
function drawLightRays() {
if (!state.showRays) return;
const cx = state.mouseX;
const cy = state.mouseY;
const rays = 12;
const maxRadius = Math.max(state.width, state.height) * state.rayLength * 1.5;
ctx.save();
ctx.globalCompositeOperation = 'screen'; // Additive blending for light
// Base glow
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, maxRadius);
gradient.addColorStop(0, `rgba(255, 255, 240, ${state.intensity * 0.8})`);
gradient.addColorStop(0.1, `rgba(200, 220, 255, ${state.intensity * 0.3})`);
gradient.addColorStop(0.5, `rgba(100, 150, 255, ${state.intensity * 0.1})`);
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, state.width, state.height);
// Rotating Shafts
if (state.autoRotate) state.time += 0.002;
for (let i = 0; i < rays; i++) {
const angle = (Math.PI * 2 / rays) * i + state.time;
ctx.translate(cx, cy);
ctx.rotate(angle);
// Draw Ray
ctx.beginPath();
ctx.moveTo(0, 0);
// Widen the ray as it goes out
const widthSpread = 150 + Math.sin(i + state.time * 5) * 50;
ctx.lineTo(maxRadius, -widthSpread / 2);
ctx.lineTo(maxRadius, widthSpread / 2);
ctx.closePath();
// Gradient for ray
const rayGrad = ctx.createLinearGradient(0, 0, maxRadius, 0);
rayGrad.addColorStop(0, `rgba(255, 255, 255, ${state.intensity * 0.4})`);
rayGrad.addColorStop(0.2, `rgba(200, 220, 255, ${state.intensity * 0.1})`);
rayGrad.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = rayGrad;
ctx.fill();
// Reset matrix
ctx.rotate(-angle);
ctx.translate(-cx, -cy);
}
ctx.restore();
}
// --- Main Render Loop ---
function animate() {
requestAnimationFrame(animate);
state.time += 1;
// 1. Clear and apply Grading Filters to the WHOLE context
// We use CSS filters on the context for "Global" grading
const brightness = state.exposure / 100;
const contrast = state.contrast / 100;
const saturate = state.saturation / 100;
// Note: Canvas API filter support is good in modern browsers
ctx.filter = `brightness(${brightness}) contrast(${contrast}) saturate(${saturate})`;
// Fill Background (Dark Room)
// Create a very subtle vignette
const bgGradient = ctx.createRadialGradient(state.width/2, state.height/2, 100, state.width/2, state.height/2, state.height);
bgGradient.addColorStop(0, '#1a1a20'); // Dark Blue-Grey
bgGradient.addColorStop(1, '#050505'); // Almost Black
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, state.width, state.height);
// 2. Draw Volumetric Light (Behind dust)
// We use 'source-over' normally, but for rays we might want screen.
// However, applying the filter above makes everything graded.
// To make rays look "colored" before grading, we draw them, then apply filter?
// Actually, ctx.filter applies to drawing operations.
// But if we want the grading to affect everything uniformly, we keep it.
// However, let's draw Rays without the filter first to keep them "hot",
// then draw background/dust, then apply filter?
// For simplicity and performance in this single-file demo, we apply grading to everything.
drawLightRays();
// 3. Draw Light Source Body (The "Sun" or Bulb)
ctx.filter = 'none'; // Turn off filter for the source core to make it pop
const lx = state.mouseX;
const ly = state.mouseY;
const lightGrad = ctx.createRadialGradient(lx, ly, 0, lx, ly, 60 * state.intensity + 20);
lightGrad.addColorStop(0, 'rgba(255, 255, 255, 1)');
lightGrad.addColorStop(0.2, 'rgba(240, 245, 255, 0.8)');
lightGrad.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = lightGrad;
ctx.beginPath();
ctx.arc(lx, ly, 60, 0, Math.PI*2);
ctx.fill();
// 4. Draw Particles (Dust)
// Particles are affected by the global filter if we don't reset,
// which helps integrate them into the "scene".
// Let's re-enable filter for dust to tint them based on grading
ctx.filter = `brightness(${brightness}) contrast(${contrast}) saturate(${saturate})`;
if (state.showDust) {
for (let i = 0; i < state.particleCount; i++) {
if (particles[i]) {
particles[i].update();
particles[i].draw();
}
}
}
// 5. Post-Processing Overlay (Vignette & Noise)
// We apply this ON TOP of everything using a temporary canvas or just drawing over
ctx.filter = 'none';
// Draw a vignette overlay to darken corners
const vigGrad = ctx.createRadialGradient(state.width/2, state.height/2, state.height/3, state.width/2, state.height/2, state.height);
vigGrad.addColorStop(0, 'rgba(0,0,0,0)');
vigGrad.addColorStop(1, 'rgba(0,0,0,0.6)');
ctx.fillStyle = vigGrad;
ctx.fillRect(0, 0, state.width, state.height);
// Scanlines / Grid effect (Subtle)
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
for(let y = 0; y < state.height; y+=4) {
ctx.fillRect(0, y, state.width, 1);
}
}
// --- UI Logic ---
const bindInput = (id, key, suffix = '%') => {
const input = document.getElementById(id);
const display = document.getElementById(`val-${id}`);
input.addEventListener('input', (e) => {
state[key] = parseFloat(e.target.value);
display.innerText = state[key] + suffix;
});
};
bindInput('exposure', 'exposure');
bindInput('contrast', 'contrast');
bindInput('saturation', 'saturation');
bindInput('intensity', 'intensity', '');
bindInput('particles', 'particleCount');
bindInput('rays', 'rayLength');
// Toggles
document.getElementById('btn-dust').addEventListener('click', (e) => {
state.showDust = !state.showDust;
e.target.classList.toggle('active');
});
document.getElementById('btn-rays').addEventListener('click', (e) => {
state.showRays = !state.showRays;
e.target.classList.toggle('active');
});
document.getElementById('btn-animate').addEventListener('click', (e) => {
state.autoRotate = !state.autoRotate;
e.target.classList.toggle('active');
});
// Start
animate();
</script>
</body>
</html>