Spaces:
Running
Running
<html lang="ko"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Pulsar Mini</title> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<!-- Tailwind CDN (λΉ λ₯Έ νλ‘ν νμ΄νμ©) --> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* custom utility μ‘°ν© */ | |
body { @apply bg-gray-900 text-gray-100 flex flex-col items-center py-6 gap-6; } | |
canvas { image-rendering: pixelated; } | |
</style> | |
</head> | |
<body> | |
<h1 class="text-3xl font-bold">Pulsar Mini</h1> | |
<div class="flex flex-wrap gap-4 max-w-4xl w-full justify-center"> | |
<!-- Canvas μμ --> | |
<canvas id="canvas" width="320" height="320" class="border border-gray-700 rounded"></canvas> | |
<!-- 컨νΈλ‘€ --> | |
<div class="flex flex-col gap-3 w-80"> | |
<label class="text-sm font-mono">μ½λ:</label> | |
<textarea | |
id="code" | |
rows="4" | |
maxlength="300" | |
class="w-full p-2 rounded bg-gray-800 font-mono text-xs resize-none outline-none" | |
>(x,y,t)=>Math.sin(x*10+t)+Math.cos(y*10+t)</textarea> | |
<div class="flex gap-2"> | |
<button id="play" class="px-3 py-1 rounded bg-green-600 text-sm">βΆ Play</button> | |
<button id="pause" class="px-3 py-1 rounded bg-red-600 text-sm hidden">ββ Pause</button> | |
<button id="random" class="px-3 py-1 rounded bg-blue-600 text-sm">π² Random</button> | |
</div> | |
<p class="text-[11px] text-gray-400 leading-snug"> | |
ν¨μλ <code>(x,y,t,i) => κ°</code> νμμ΄μ΄μΌ ν©λλ€.<br> | |
<code>x</code>, <code>y</code> λ 0~1 μ’ν, <code>t</code> λ μ΄, <code>i</code>λ ν½μ μΈλ±μ€μ λλ€.<br> | |
λ°νκ°μ 0~1 λ²μλ‘ ν΄λ¨νλμ΄ κ·Έλ μ΄μ€μΌμΌ λ°κΈ°λ‘ ννλ©λλ€. | |
</p> | |
</div> | |
</div> | |
<script> | |
const canvas = document.getElementById('canvas'); | |
const ctx = canvas.getContext('2d'); | |
const codeEl = document.getElementById('code'); | |
const playBtn = document.getElementById('play'); | |
const pauseBtn= document.getElementById('pause'); | |
const rndBtn = document.getElementById('random'); | |
const EXAMPLES = [ | |
'(x,y,t)=>Math.sin((x+y+t)*6)', | |
'(x,y,t)=>Math.sin(x*10+t)+Math.cos(y*10+t)', | |
'(x,y,t)=>Math.sin(Math.hypot(x-0.5,y-0.5)*20-t*2)', | |
]; | |
let fn = compile(codeEl.value); | |
let playing = false; | |
let start = performance.now(); | |
function compile(src){ | |
try { return eval(src); } | |
catch(e){ console.error(e); return ()=>0; } | |
} | |
function draw(time){ | |
const t = (time - start) / 1000; | |
const w = canvas.width; | |
const h = canvas.height; | |
const img = ctx.getImageData(0,0,w,h); | |
const data = img.data; | |
for(let y=0; y<h; y++){ | |
for(let x=0; x<w; x++){ | |
const idx = (y*w + x) * 4; | |
const v = Math.max(0, Math.min(1, fn(x/w, y/h, t, idx))); | |
const c = v * 255; | |
data[idx] = data[idx+1] = data[idx+2] = c; | |
data[idx+3] = 255; | |
} | |
} | |
ctx.putImageData(img,0,0); | |
if(playing) requestAnimationFrame(draw); | |
} | |
draw(start); | |
// event handlers | |
playBtn.addEventListener('click', () => { | |
playing = true; | |
playBtn.classList.add('hidden'); | |
pauseBtn.classList.remove('hidden'); | |
requestAnimationFrame(draw); | |
}); | |
pauseBtn.addEventListener('click', () => { | |
playing = false; | |
pauseBtn.classList.add('hidden'); | |
playBtn.classList.remove('hidden'); | |
}); | |
codeEl.addEventListener('input', () => { | |
fn = compile(codeEl.value); | |
}); | |
rndBtn.addEventListener('click', () => { | |
codeEl.value = EXAMPLES[Math.floor(Math.random()*EXAMPLES.length)]; | |
fn = compile(codeEl.value); | |
}); | |
</script> | |
</body> | |
</html> |