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 โ 16x16 ๋ํธ ์ปฌ๋ฌ</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* ๊ธฐ๋ณธ ๋ ์ด์์ */ | |
body { | |
margin: 0; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
height: 100vh; | |
background-color: black; | |
color: #e5e7eb; | |
font-family: sans-serif; | |
overflow: hidden; | |
} | |
.canvas-container { | |
position: relative; | |
overflow: hidden; | |
border-radius: 0.5rem; | |
border: 1px solid #374151; | |
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
} | |
canvas { | |
display: block; | |
image-rendering: pixelated; | |
image-rendering: crisp-edges; | |
cursor: crosshair; | |
} | |
/* ์ค๋ฒ๋ ์ด UI */ | |
#ui { | |
position: absolute; | |
top: 0.75rem; | |
left: 50%; | |
transform: translateX(-50%); | |
display: flex; | |
gap: 0.5rem; | |
z-index: 50; | |
user-select: none; | |
} | |
#ui button { | |
padding: 0.375rem 1rem; | |
border-radius: 0.25rem; | |
font-size: 0.875rem; | |
font-weight: 600; | |
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); | |
background-color: rgba(31, 41, 55, 0.7); | |
backdrop-filter: blur(4px); | |
} | |
#ui button:hover { | |
background-color: rgba(55, 65, 81, 0.7); | |
} | |
.hidden { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<!-- ์ค์ ๋ฐฐ์น๋ ์บ๋ฒ์ค ์ปจํ ์ด๋ --> | |
<div class="canvas-container"> | |
<canvas id="canvas"></canvas> | |
<!-- UI ์ค๋ฒ๋ ์ด --> | |
<div id="ui"> | |
<button id="play">โถ ์ฌ์</button> | |
<button id="pause" class="hidden">โโ ์ผ์์ ์ง</button> | |
<button id="random">๐ฒ ๋๋ค</button> | |
</div> | |
</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'); | |
// ๋ํธ ๊ทธ๋ฆฌ๋ ์ค์ | |
const GRID_SIZE = 16; // 16x16 ๋ํธ | |
// ๋ค์ํ ์ํ ๊ณต์์ ํ์ฉํ ์ปฌ๋ฌ ํจํด ํ ํ๋ฆฟ (R,G,B ๊ฐ๊ฐ์ ์) | |
const BASE_PATTERNS = [ | |
// 1. ์ฌ์ธ/์ฝ์ฌ์ธ ๊ฒฉ์ | |
'(x,y,t)=>{const r=Math.sin((x+$S1)*$F1+t);const g=Math.sin((y+$S2)*$F2-t);const b=Math.cos((x+y)*$F3+t*0.5);return[(r+1)/2,(g+1)/2,(b+1)/2];}', | |
// 2. ์ํ ํ๋ | |
'(x,y,t)=>{const d=Math.hypot(x-0.5,y-0.5);return[0.5+0.5*Math.sin(d*$F1-t*2),0.5+0.5*Math.cos(d*$F2+t),0.5+0.5*Math.sin((x-y)*$F3+t)];}', | |
// 3. ๋ณต์ํจ์ ๊ธฐ๋ฐ ํจํด | |
'(x,y,t)=>{return[0.5+0.5*Math.sin((x*x-y*y)*$F1+t),0.5+0.5*Math.cos((x*y)*$F2-t),0.5+0.5*Math.sin((x+y)*$F3)];}', | |
// 4. ์ ๋๊ฐ ์ฌ์ธ โ ๋ฐ๊ณ ์ ๋ช ํ ์ | |
'(x,y,t)=>{return[Math.abs(Math.sin($F1*x+t)),Math.abs(Math.sin($F2*y-t)),Math.abs(Math.sin(($F3)*(x+y)+t*0.3))];}', | |
// 5. ๊ทน์ขํ ๋ฌด์ง๊ฐ | |
'(x,y,t)=>{const a=Math.atan2(y-0.5,x-0.5);const r=(a/Math.PI+1)/2;return[r,0.5+0.5*Math.sin(t+$F1*r*10),1-r];}', | |
// 6. ๋ก์ฆ(์ฅ๋ฏธ) ์ปค๋ธ ๊ธฐ๋ฐ | |
'(x,y,t)=>{const a=Math.atan2(y-0.5,x-0.5);const k=$K;return[0.5+0.5*Math.sin(k*a+t),0.5+0.5*Math.sin(k*a+t+2*Math.PI/3),0.5+0.5*Math.sin(k*a+t+4*Math.PI/3)];}', | |
// 7. ํ๋ฅด๋ง ๋์ ๋ ธ์ด์ฆ | |
'(x,y,t)=>{const r=Math.hypot(x-0.5,y-0.5);const phi=r*10+$F1;return[0.5+0.5*Math.sin(phi+t),0.5+0.5*Math.cos(phi*1.3-t),0.5+0.5*Math.sin(phi*0.7+t*0.5)];}', | |
// 8. ์๋ก์ด ํจํด: ๊ฒฉ์ ๋ ธ์ด์ฆ | |
'(x,y,t)=>{const nx=Math.floor(x*$F1)/($F1/2);const ny=Math.floor(y*$F2)/($F2/2);return[0.5+0.5*Math.sin(nx*$F3+t),0.5+0.5*Math.cos(ny*$F3-t),0.5+0.5*Math.sin((nx+ny)*$F3+t*0.7)];}', | |
// 9. ์๋ก์ด ํจํด: ํ๋ํ ๊ธฐ๋ฐ | |
'(x,y,t)=>{let zx=3*(x-0.5),zy=3*(y-0.5),i=0,max=$IT;while(i<max&&zx*zx+zy*zy<4){const tmp=zx*zx-zy*zy;zy=2*zx*zy+0.5*Math.sin(t);zx=tmp+0.5*Math.cos(t);i++;}return[i/max,0.5+0.5*Math.sin(i/max*Math.PI+t),0.5+0.5*Math.cos(i/max*Math.PI*2-t)];}', | |
// 10. ์๋ก์ด ํจํด: ์ฒด์ปค๋ณด๋ ๋ณํ | |
'(x,y,t)=>{const cx=Math.floor(x*$F1);const cy=Math.floor(y*$F1);const even=(cx+cy)%2===0;const r=0.5+0.5*Math.sin(cx*cy/($F2)+t);const g=0.5+0.5*Math.cos((cx-cy)*$F3-t);const b=even?0.2+0.8*Math.sin(t*0.5):0.2+0.8*Math.cos(t*0.7);return[r,g,b];}', | |
// 11. ์๋ก์ด ํจํด: ๋ชจ์๋ ํจํด | |
'(x,y,t)=>{const p1=Math.sin(x*$F1+t);const p2=Math.sin(y*$F2-t*0.7);const p3=Math.sin((x+y)*$F3+t*0.3);const moire=Math.sin(p1*p2*p3*10);return[0.5+0.5*moire,0.5+0.5*Math.sin(moire*Math.PI+t),0.5+0.5*Math.cos(moire*Math.PI-t)];}', | |
// 12. ์๋ก์ด ํจํด: ์ง๋ ๋ฌผ๊ฒฐ | |
'(x,y,t)=>{const wave1=Math.sin(y*$F1+x*$F2+t);const wave2=Math.sin(x*$F3-y*$F1-t*1.5);return[0.5+0.5*wave1,0.5+0.5*wave2,0.5+0.5*Math.sin(wave1*wave2*3+t)];}', | |
// 13. ์๋ก์ด ํจํด: ๋ ธ์ด์ฆ ํผ๋ฎคํ ์ด์ | |
'(x,y,t)=>{const p=(x*$F1+y*$F2)%1;const q=(y*$F3-x*$F2+t)%1;return[p,q,0.5+0.5*Math.sin(p*q*Math.PI*2+t)];}', | |
// 14. ์๋ก์ด ํจํด: ๋ํธ ๊ทธ๋ฆฌ๋ | |
'(x,y,t)=>{const gx=Math.floor(x*4)/4;const gy=Math.floor(y*4)/4;const r=0.5+0.5*Math.sin(gx*$F1+gy*$F2+t);const g=0.5+0.5*Math.cos(gx*$F3-gy*$F1-t);const b=0.5+0.5*Math.sin((gx-gy)*$F3*2+t*1.5);return[r,g,b];}', | |
]; | |
function rand(min,max){return Math.random()*(max-min)+min;} | |
function pickRandomFormula(){ | |
const tmpl=BASE_PATTERNS[Math.floor(Math.random()*BASE_PATTERNS.length)]; | |
return tmpl | |
.replaceAll('$F1',rand(5,40).toFixed(2)) | |
.replaceAll('$F2',rand(5,40).toFixed(2)) | |
.replaceAll('$F3',rand(5,40).toFixed(2)) | |
.replaceAll('$S1',Math.random().toFixed(2)) | |
.replaceAll('$S2',Math.random().toFixed(2)) | |
.replaceAll('$K',Math.floor(rand(3,12))) // ์ ์ ๊ฝ์ ์ | |
.replaceAll('$IT',Math.floor(rand(10,30))); // ํ๋ํ ๋ฐ๋ณต ํ์ | |
} | |
let formulaSrc = pickRandomFormula(); | |
let fn = compile(formulaSrc); | |
let playing = true; | |
let start = performance.now(); | |
function resizeCanvas(){ | |
// ๋ทฐํฌํธ ํฌ๊ธฐ์ 70%๋ฅผ ์ฐจ์งํ๋ ์ ์ฌ๊ฐํ์ผ๋ก ์ค์ | |
const size = Math.min(window.innerWidth, window.innerHeight) * 0.7; | |
// ์บ๋ฒ์ค์ ์ค์ ํด์๋๋ 16x16 | |
canvas.width = GRID_SIZE; | |
canvas.height = GRID_SIZE; | |
// ์บ๋ฒ์ค ํ์ ํฌ๊ธฐ๋ ํ๋ฉด์ 70% | |
canvas.style.width = `${size}px`; | |
canvas.style.height = `${size}px`; | |
// ์บ๋ฒ์ค ์ปจํ ์ด๋๋ ๊ฐ์ ํฌ๊ธฐ๋ก ์ค์ | |
const container = document.querySelector('.canvas-container'); | |
container.style.width = `${size}px`; | |
container.style.height = `${size}px`; | |
} | |
resizeCanvas(); | |
window.addEventListener('resize',resizeCanvas); | |
function compile(src){ | |
try{ return eval(src); } | |
catch(e){ console.error(e); return ()=>[0,0,0]; } | |
} | |
function clamp01(v){ return v<0?0:v>1?1:v; } | |
function draw(time){ | |
const t = (time - start)/1000; | |
const w = canvas.width; | |
const h = canvas.height; | |
const img = ctx.createImageData(w,h); | |
const data = img.data; | |
let idx=0; | |
for(let y=0;y<h;y++){ | |
for(let x=0;x<w;x++){ | |
const c = fn(x/w,y/h,t,idx); | |
const r = clamp01(c[0])*255; | |
const g = clamp01(c[1])*255; | |
const b = clamp01(c[2])*255; | |
data[idx++] = r; | |
data[idx++] = g; | |
data[idx++] = b; | |
data[idx++] = 255; | |
} | |
} | |
ctx.putImageData(img,0,0); | |
if(playing) requestAnimationFrame(draw); | |
} | |
requestAnimationFrame(draw); | |
function randomize(){ formulaSrc = pickRandomFormula(); fn = compile(formulaSrc); start = performance.now(); } | |
// UI ์ด๋ฒคํธ | |
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> |