File size: 4,293 Bytes
9d4c549
 
 
df6a4c8
9d4c549
df6a4c8
 
 
9d4c549
 
df6a4c8
 
 
 
 
 
 
9d4c549
 
 
df6a4c8
 
9d4c549
df6a4c8
9d4c549
df6a4c8
 
 
 
9d4c549
 
df6a4c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d4c549
df6a4c8
 
 
 
 
9d4c549
df6a4c8
 
 
 
9d4c549
df6a4c8
 
 
 
 
 
 
 
 
 
9d4c549
df6a4c8
 
 
 
 
 
9d4c549
 
 
df6a4c8
 
 
 
9d4c549
df6a4c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9d4c549
 
df6a4c8
 
 
 
 
 
 
 
 
 
 
 
 
 
9d4c549
df6a4c8
 
 
 
 
9d4c549
df6a4c8
 
 
9d4c549
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<!DOCTYPE html>
<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>