Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Pulsar Mini โ Touch Reactive</title> | |
| <!-- Tailwind CDN: ํ๋กํ ํ์ ์ฉ, ํ์ ์ ๋ก์ปฌ ๋น๋๋ก ๊ต์ฒด --> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| @apply bg-gray-900 text-gray-100 flex flex-col items-center justify-center min-h-screen gap-6 p-4 select-none; | |
| } | |
| canvas { | |
| image-rendering: pixelated; | |
| @apply border border-gray-700 rounded shadow-lg; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1 class="text-3xl font-bold mb-2">Pulsar Mini</h1> | |
| <p class="text-sm text-gray-400 mb-4 text-center">์บ๋ฒ์ค๋ฅผ ํญํ๊ฑฐ๋ ํด๋ฆญํ ๋๋ง๋ค ์๋ก์ด ๋ฌด์์ ํจํด์ด ์ฌ์๋ฉ๋๋ค.</p> | |
| <canvas id="canvas"></canvas> | |
| <div class="flex gap-3"> | |
| <button id="play" class="px-4 py-1.5 rounded bg-green-600 text-sm">โถ ์ฌ์</button> | |
| <button id="pause" class="px-4 py-1.5 rounded bg-red-600 text-sm hidden">โโ ์ผ์์ ์ง</button> | |
| <button id="random" class="px-4 py-1.5 rounded bg-blue-600 text-sm">๐ฒ ๋๋ค</button> | |
| </div> | |
| <script> | |
| const DPR = window.devicePixelRatio || 1; | |
| const canvas = document.getElementById('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const playBtn = document.getElementById('play'); | |
| const pauseBtn = document.getElementById('pause'); | |
| const rndBtn = document.getElementById('random'); | |
| // 10๊ฐ์ง ์์ + ๋งค ํธ์ถ๋ง๋ค ๋งค๊ฐ๋ณ์๋ฅผ ๋๋ค์ผ๋ก ์์ | |
| const BASE_PATTERNS = [ | |
| '(x,y,t)=>Math.sin((x+y+t)*$FREQ)', | |
| '(x,y,t)=>Math.cos((x-y+t)*$FREQ)', | |
| '(x,y,t)=>Math.sin(Math.hypot(x-0.5,y-0.5)*$FREQ - t*3)', | |
| '(x,y,t)=>Math.sin(x*$FREQ+t)+Math.cos(y*$FREQ+t)', | |
| '(x,y,t)=>Math.sin((x*$FREQ+y*$FREQ+t*2))*Math.cos((x*$FREQ-y*$FREQ+t))', | |
| '(x,y,t)=>Math.sin((x+y)*$FREQ + t*5)*0.5+0.5', | |
| '(x,y,t)=>Math.sin(Math.atan2(y-0.5,x-0.5)*$FREQ + t*2)', | |
| '(x,y,t)=>((Math.sin(x*$FREQ)+Math.cos(y*$FREQ+t))*0.5)+0.5', | |
| '(x,y,t)=>Math.sin(((x-0.5)**2+(y-0.5)**2)*$FREQ - t*4)', | |
| '(x,y,t)=>Math.sin((x*x - y*y)*$FREQ + t*3)' | |
| ]; | |
| function pickRandomFormula() { | |
| const tmpl = BASE_PATTERNS[Math.floor(Math.random()*BASE_PATTERNS.length)]; | |
| const freq = (Math.random()*30 + 10).toFixed(1); // 10~40 ์ฌ์ด ์ฃผํ์ | |
| return tmpl.replaceAll('$FREQ', freq); | |
| } | |
| let formulaSrc = pickRandomFormula(); | |
| let fn = compile(formulaSrc); | |
| let playing = true; | |
| let start = performance.now(); | |
| function resizeCanvas() { | |
| const size = Math.min(window.innerWidth, window.innerHeight) * 0.8; | |
| canvas.style.width = size + 'px'; | |
| canvas.style.height = size + 'px'; | |
| canvas.width = size * DPR; | |
| canvas.height = size * DPR; | |
| ctx.scale(DPR, DPR); | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| 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 / DPR; | |
| const h = canvas.height / DPR; | |
| const img = ctx.createImageData(w, h); | |
| const data = img.data; | |
| let i = 0; | |
| for (let y = 0; y < h; y++) { | |
| for (let x = 0; x < w; x++) { | |
| const v = Math.max(0, Math.min(1, fn(x / w, y / h, t, i))); | |
| const c = v * 255; | |
| data[i++] = c; // R | |
| data[i++] = c; // G | |
| data[i++] = c; // B | |
| data[i++] = 255; // A | |
| } | |
| } | |
| ctx.putImageData(img, 0, 0); | |
| if (playing) requestAnimationFrame(draw); | |
| } | |
| requestAnimationFrame(draw); | |
| function randomize() { | |
| formulaSrc = pickRandomFormula(); | |
| fn = compile(formulaSrc); | |
| start = performance.now(); | |
| } | |
| // ๋ฒํผ ์ด๋ฒคํธ | |
| playBtn.addEventListener('click', () => { | |
| playing = true; | |
| playBtn.classList.add('hidden'); | |
| pauseBtn.classList.remove('hidden'); | |
| start = performance.now(); | |
| requestAnimationFrame(draw); | |
| }); | |
| pauseBtn.addEventListener('click', () => { | |
| playing = false; | |
| pauseBtn.classList.add('hidden'); | |
| playBtn.classList.remove('hidden'); | |
| }); | |
| rndBtn.addEventListener('click', randomize); | |
| canvas.addEventListener('pointerdown', randomize); // ํฐ์นยทํด๋ฆญ ๋ชจ๋ ๋์ | |
| </script> | |
| </body> | |
| </html> |