anycoder-e7a412f4 / index.html
matthewspring's picture
Upload folder using huggingface_hub
8c2bc39 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cosmic Canvas | Interactive Particle Art</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tone/14.8.49/Tone.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;500;700&display=swap" rel="stylesheet">
<style>
:root {
--primary: #00f3ff;
--secondary: #ff00aa;
--dark: #0a0a12;
--glass: rgba(255, 255, 255, 0.05);
}
body {
margin: 0;
overflow: hidden;
background-color: var(--dark);
font-family: 'Space Grotesk', sans-serif;
color: white;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
/* Custom Scrollbar for panels */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-track {
background: rgba(0,0,0,0.3);
}
::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 3px;
}
/* Glassmorphism Utilities */
.glass-panel {
background: var(--glass);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
}
.neon-text {
text-shadow: 0 0 5px var(--primary), 0 0 10px var(--primary);
}
.neon-text-pink {
text-shadow: 0 0 5px var(--secondary), 0 0 10px var(--secondary);
}
/* Range Slider Styling */
input[type=range] {
-webkit-appearance: none;
width: 100%;
background: transparent;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
height: 16px;
width: 16px;
border-radius: 50%;
background: var(--primary);
cursor: pointer;
margin-top: -6px;
box-shadow: 0 0 10px var(--primary);
}
input[type=range]::-webkit-slider-runnable-track {
width: 100%;
height: 4px;
cursor: pointer;
background: rgba(255,255,255,0.2);
border-radius: 2px;
}
/* Animations */
@keyframes float {
0% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0px); }
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 10px var(--primary); }
50% { box-shadow: 0 0 25px var(--primary), 0 0 10px var(--secondary); }
}
.btn-glow:hover {
animation: pulse-glow 1.5s infinite;
}
.fade-in {
animation: fadeIn 0.5s ease-in forwards;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body class="antialiased selection:bg-cyan-500 selection:text-black">
<!-- Canvas Layer -->
<canvas id="canvas1"></canvas>
<!-- UI Overlay -->
<div class="relative z-10 w-full h-screen pointer-events-none flex flex-col justify-between p-4 md:p-8">
<!-- Header -->
<header class="flex justify-between items-start pointer-events-auto">
<div class="glass-panel p-4 rounded-xl border-l-4 border-cyan-400 animate-float">
<h1 class="text-3xl md:text-5xl font-bold tracking-tighter neon-text">COSMIC<span class="text-pink-500 neon-text-pink">CANVAS</span></h1>
<p class="text-xs md:text-sm text-gray-300 mt-1 tracking-widest uppercase">Interactive Particle Simulation</p>
<div class="mt-2 text-[10px] text-gray-400">
Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="underline hover:text-cyan-400 transition-colors">anycoder</a>
</div>
</div>
<div class="flex gap-2">
<button id="audioBtn" class="glass-panel p-3 rounded-full hover:bg-white/10 transition-all btn-glow group" title="Toggle Ambient Audio">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-cyan-400 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" />
</svg>
</button>
<button id="infoBtn" class="glass-panel p-3 rounded-full hover:bg-white/10 transition-all btn-glow group">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-pink-500 group-hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
</div>
</header>
<!-- Center Interaction Hint -->
<div id="hint" class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center pointer-events-none transition-opacity duration-500">
<p class="text-cyan-200 text-lg md:text-2xl font-light tracking-widest opacity-70">CLICK & DRAG TO CREATE</p>
<p class="text-gray-400 text-sm mt-2">MOVE MOUSE TO INTERACT</p>
</div>
<!-- Controls Footer -->
<footer class="pointer-events-auto w-full max-w-4xl mx-auto">
<div class="glass-panel rounded-2xl p-4 md:p-6 grid grid-cols-1 md:grid-cols-4 gap-6 items-end">
<!-- Mode Selector -->
<div class="col-span-1 md:col-span-1">
<label class="block text-xs font-bold text-cyan-400 uppercase tracking-wider mb-2">Simulation Mode</label>
<div class="flex gap-2">
<button class="mode-btn flex-1 py-2 text-xs border border-cyan-500/30 rounded bg-cyan-500/20 text-cyan-300 hover:bg-cyan-500 hover:text-black transition-all active" data-mode="particles">Flow</button>
<button class="mode-btn flex-1 py-2 text-xs border border-pink-500/30 rounded bg-pink-500/20 text-pink-300 hover:bg-pink-500 hover:text-black transition-all" data-mode="constellation">Web</button>
</div>
</div>
<!-- Sliders -->
<div class="col-span-1 md:col-span-2 grid grid-cols-2 gap-4">
<div>
<div class="flex justify-between text-xs text-gray-400 mb-1">
<span>Particle Count</span>
<span id="countVal">150</span>
</div>
<input type="range" id="particleCount" min="50" max="400" value="150">
</div>
<div>
<div class="flex justify-between text-xs text-gray-400 mb-1">
<span>Speed</span>
<span id="speedVal">1.0x</span>
</div>
<input type="range" id="speedControl" min="0.1" max="3.0" step="0.1" value="1.0">
</div>
</div>
<!-- Actions -->
<div class="col-span-1 md:col-span-1 flex gap-2">
<button id="resetBtn" class="flex-1 bg-gray-700 hover:bg-gray-600 text-white py-2 px-4 rounded text-sm font-medium transition-colors border border-gray-600">
Reset
</button>
<button id="colorBtn" class="flex-1 bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-400 hover:to-blue-500 text-white py-2 px-4 rounded text-sm font-medium transition-all shadow-lg shadow-cyan-500/30">
Theme
</button>
</div>
</div>
</footer>
</div>
<!-- Info Modal -->
<div id="infoModal" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm opacity-0 pointer-events-none transition-opacity duration-300">
<div class="glass-panel max-w-md w-full p-8 rounded-2xl transform scale-95 transition-transform duration-300" id="modalContent">
<h2 class="text-2xl font-bold text-white mb-4">How to use Cosmic Canvas</h2>
<ul class="space-y-3 text-gray-300 text-sm">
<li class="flex items-start"><span class="text-cyan-400 mr-2"></span> <strong>Flow Mode:</strong> Particles follow the mouse and leave a cosmic trail.</li>
<li class="flex items-start"><span class="text-pink-400 mr-2"></span> <strong>Web Mode:</strong> Particles connect to each other forming a neural network.</li>
<li class="flex items-start"><span class="text-yellow-400 mr-2"></span> <strong>Audio:</strong> Toggle the ambient soundscape for immersion.</li>
<li class="flex items-start"><span class="text-green-400 mr-2"></span> <strong>Click & Drag:</strong> Creates a burst of energy and sound.</li>
</ul>
<button id="closeModal" class="mt-6 w-full bg-white/10 hover:bg-white/20 text-white py-3 rounded-lg transition-colors border border-white/10">
Start Creating
</button>
</div>
</div>
<script>
/**
* COSMIC CANVAS ENGINE
* Handles Particle System, Audio Synthesis, and UI Logic
*/
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
// State
let particlesArray = [];
let hue = 0;
let isMouseDown = false;
let mode = 'particles'; // 'particles' or 'constellation'
let animationId;
let audioStarted = false;
// Configuration
const config = {
particleCount: 150,
baseSpeed: 1.0,
connectionDistance: 100,
mouseRadius: 150,
themeHue: 0 // 0 = Cyan/Blue, 180 = Purple/Pink, etc.
};
// Mouse Object
const mouse = {
x: undefined,
y: undefined,
}
// Resize Handling
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
init();
});
// Mouse Events
window.addEventListener('mousemove', (e) => {
mouse.x = e.x;
mouse.y = e.y;
// Hide hint on first interaction
const hint = document.getElementById('hint');
if(hint.style.opacity !== '0') hint.style.opacity = '0';
});
window.addEventListener('mousedown', () => {
isMouseDown = true;
if(audioStarted) playBurstSound();
});
window.addEventListener('mouseup', () => {
isMouseDown = false;
});
window.addEventListener('mouseout', () => {
mouse.x = undefined;
mouse.y = undefined;
});
// --- Audio System (Tone.js) ---
let synth, drone, filter;
async function initAudio() {
await Tone.start();
// PolySynth for interaction sounds
synth = new Tone.PolySynth(Tone.Synth, {
oscillator: { type: "fatsawtooth" },
envelope: { attack: 0.01, decay: 0.1, sustain: 0.1, release: 1 }
}).toDestination();
synth.volume.value = -10;
// Drone for background ambience
filter = new Tone.AutoFilter({
frequency: 0.1,
baseFrequency: 200,
octaves: 2.6
}).toDestination().start();
drone = new Tone.Oscillator(110, "sine").connect(filter).start();
drone.volume.value = -20;
// Reverb for spacey feel
const reverb = new Tone.Reverb({ decay: 5, wet: 0.5 }).toDestination();
synth.connect(reverb);
drone.connect(reverb);
audioStarted = true;
}
function playBurstSound() {
if(!synth) return;
const notes = ["C4", "E4", "G4", "A4", "C5"];
const note = notes[Math.floor(Math.random() * notes.length)];
synth.triggerAttackRelease(note, "8n");
}
function toggleAudio() {
if (!audioStarted) {
initAudio();
document.getElementById('audioBtn').classList.add('bg-cyan-500/40');
} else {
Tone.Destination.mute = !Tone.Destination.mute;
document.getElementById('audioBtn').classList.toggle('bg-cyan-500/40');
}
}
// --- Particle Class ---
class Particle {
constructor() {
this.x = Math.random() * canvas.width;
this.y = Math.random() * canvas.height;
this.size = Math.random() * 3 + 1;
// Random velocity vector
this.speedX = (Math.random() * 3 - 1.5) * config.baseSpeed;
this.speedY = (Math.random() * 3 - 1.5) * config.baseSpeed;
this.color = `hsl(${config.themeHue + Math.random() * 60}, 100%, 50%)`;
}
update() {
// Movement
this.x += this.speedX;
this.y += this.speedY;
// Bounce off edges
if (this.x > canvas.width || this.x < 0) this.speedX = -this.speedX;
if (this.y > canvas.height || this.y < 0) this.speedY = -this.speedY;
// Mouse Interaction
if (mouse.x != undefined) {
let dx = mouse.x - this.x;
let dy = mouse.y - this.y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.mouseRadius) {
const forceDirectionX = dx / distance;
const forceDirectionY = dy / distance;
const force = (config.mouseRadius - distance) / config.mouseRadius;
// Push away or attract based on click
const directionMultiplier = isMouseDown ? 1 : -1;
const directionX = forceDirectionX * force * directionMultiplier * 5;
const directionY = forceDirectionY * force * directionMultiplier * 5;
this.x += directionX;
this.y += directionY;
}
}
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
// --- System Logic ---
function init() {
particlesArray = [];
for (let i = 0; i < config.particleCount; i++) {
particlesArray.push(new Particle());
}
}
function handleParticles() {
for (let i = 0; i < particlesArray.length; i++) {
particlesArray[i].update();
particlesArray[i].draw();
// Constellation Mode Logic
if (mode === 'constellation') {
for (let j = i; j < particlesArray.length; j++) {
let dx = particlesArray[i].x - particlesArray[j].x;
let dy = particlesArray[i].y - particlesArray[j].y;
let distance = Math.sqrt(dx * dx + dy * dy);
if (distance < config.connectionDistance) {
ctx.beginPath();
// Dynamic opacity based on distance
let opacity = 1 - (distance / config.connectionDistance);
ctx.strokeStyle = `hsla(${config.themeHue}, 100%, 50%, ${opacity})`;
ctx.lineWidth = 1;
ctx.moveTo(particlesArray[i].x, particlesArray[i].y);
ctx.lineTo(particlesArray[j].x, particlesArray[j].y);
ctx.stroke();
}
}
}
}
}
function animate() {
// Clear canvas with trail effect
ctx.fillStyle = 'rgba(10, 10, 18, 0.15)'; // Low opacity for trails
ctx.fillRect(0, 0, canvas.width, canvas.height);
handleParticles();
// Cycle global hue slowly for dynamic color shifting if not using fixed theme
// hue += 0.5;
animationId = requestAnimationFrame(animate);
}
// --- UI Logic ---
// Mode Switching
const modeBtns = document.querySelectorAll('.mode-btn');
modeBtns.forEach(btn => {
btn.addEventListener('click', (e) => {
// Update UI
modeBtns.forEach(b => {
b.classList.remove('active', 'bg-cyan-500', 'text-black', 'bg-pink-500');
// Reset to default styles based on type
if(b.dataset.mode === 'particles') {
b.className = 'mode-btn flex-1 py-2 text-xs border border-cyan-500/30 rounded bg-cyan-500/20 text-cyan-300 hover:bg-cyan-500 hover:text-black transition-all';
} else {
b.className = 'mode-btn flex-1 py-2 text-xs border border-pink-500/30 rounded bg-pink-500/20 text-pink-300 hover:bg-pink-500 hover:text-black transition-all';
}
});
mode = e.target.dataset.mode;
// Set Active Style
if(mode === 'particles') {
e.target.classList.add('bg-cyan-500', 'text-black');
} else {
e.target.classList.add('bg-pink-500', 'text-black');
}
});
});
// Controls
document.getElementById('particleCount').addEventListener('input', (e) => {
config.particleCount = parseInt(e.target.value);
document.getElementById('countVal').innerText = config.particleCount;
init();
});
document.getElementById('speedControl').addEventListener('input', (e) => {
config.baseSpeed = parseFloat(e.target.value);
document.getElementById('speedVal').innerText = config.baseSpeed + 'x';
// Update existing particles speed
particlesArray.forEach(p => {
p.speedX = (Math.random() * 3 - 1.5) * config.baseSpeed;
p.speedY = (Math.random() * 3 - 1.5) * config.baseSpeed;
});
});
document.getElementById('resetBtn').addEventListener('click', () => {
init();
});
document.getElementById('colorBtn').addEventListener('click', () => {
// Shift hue theme
config.themeHue = (config.themeHue + 60) % 360;
// Update particle colors
particlesArray.forEach(p => {
p.color = `hsl(${config.themeHue + Math.random() * 60}, 100%, 50%)`;
});
});
// Audio Toggle
document.getElementById('audioBtn').addEventListener('click', toggleAudio);
// Modal Logic
const modal = document.getElementById('infoModal');
const modalContent = document.getElementById('modalContent');
const infoBtn = document.getElementById('infoBtn');
const closeModal = document.getElementById('closeModal');
function openModal() {
modal.classList.remove('pointer-events-none', 'opacity-0');
modalContent.classList.remove('scale-95');
modalContent.classList.add('scale-100');
}
function hideModal() {
modal.classList.add('pointer-events-none', 'opacity-0');
modalContent.classList.remove('scale-100');
modalContent.classList.add('scale-95');
}
infoBtn.addEventListener('click', openModal);
closeModal.addEventListener('click', hideModal);
modal.addEventListener('click', (e) => {
if(e.target === modal) hideModal();
});
// Initialization
init();
animate();
// Show modal on first load after a short delay
setTimeout(() => {
// Check if user has interacted, if not, maybe show hint
}, 2000);
</script>
</body>
</html>