PageSourceSearch

https://oras.org/wp-content/themes/oras-theme/js/starfield.js?ver=1.2.0

js oras.org collected 2026-10-02 21:31:41 UTC 8,109 bytes, 261 lines download raw bytes

1(function () {
2  function ready(f) {
3    if (document.readyState !== 'loading') f();
4    else document.addEventListener('DOMContentLoaded', f);
5  }
6
7  ready(function () {
8    const nebulaCanvas = document.getElementById('nebula-canvas');
9    const starCanvas = document.getElementById('star-canvas');
10    if (!nebulaCanvas || !starCanvas) return;
11
12    const nebCtx = nebulaCanvas.getContext('2d', { alpha: true, desynchronized: true });
13    const starCtx = starCanvas.getContext('2d', { alpha: true, desynchronized: true });
14
15    const DPR = Math.min(window.devicePixelRatio || 1, 2);
16
17    // Cache window.innerWidth/innerHeight to avoid layout calls
18    let W = window.innerWidth;
19    let H = window.innerHeight;
20
21    function resizeCanvas() {
22      W = window.innerWidth;
23      H = window.innerHeight;
24
25      [nebulaCanvas, starCanvas].forEach((c) => {
26        c.width = Math.floor(W * DPR);
27        c.height = Math.floor(H * DPR);
28        c.style.width = W + 'px';
29        c.style.height = H + 'px';
30
31        const ctx = c.getContext('2d');
32        ctx.setTransform(1, 0, 0, 1, 0, 0);
33        ctx.scale(DPR, DPR);
34      });
35    }
36
37    // IMPORTANT: run immediately (not only on window.load)
38    resizeCanvas();
39
40    window.addEventListener('load', resizeCanvas);
41    window.addEventListener('orientationchange', resizeCanvas);
42    window.addEventListener('resize', () => {
43      clearTimeout(window._resizeTimeout);
44      window._resizeTimeout = setTimeout(() => {
45        resizeCanvas();
46        // regen without changing visuals
47        createNebula();
48        createStars();
49      }, 120);
50    });
51
52    // Cache for hex→rgb conversion (massive performance win)
53    const rgbCache = {};
54    function hexToRgb(hex) {
55      if (rgbCache[hex]) return rgbCache[hex];
56      const h = hex.replace('#', '');
57      const bigint = parseInt(h, 16);
58      const out = `${(bigint >> 16) & 255},${(bigint >> 8) & 255},${bigint & 255}`;
59      rgbCache[hex] = out;
60      return out;
61    }
62
63    // ----------------- Nebula -----------------
64    let nebulaBlobs = [];
65    const nebulaColors = [
66      'rgba(255,200,200,0.05)',
67      'rgba(180,150,255,0.05)',
68      'rgba(120,180,255,0.04)',
69      'rgba(255,150,100,0.04)',
70    ];
71
72    function createNebula() {
73      nebulaBlobs.length = 0;
74      for (let i = 0; i < 10; i++) {
75        nebulaBlobs.push({
76          x: Math.random() * W,
77          y: Math.random() * H,
78          radius: 200 + Math.random() * 300,
79          color: nebulaColors[(Math.random() * nebulaColors.length) | 0],
80          dx: (Math.random() - 0.5) * 0.05,
81          dy: (Math.random() - 0.5) * 0.05,
82          _gradient: null,
83        });
84      }
85    }
86
87    function drawNebula() {
88      nebCtx.clearRect(0, 0, W, H);
89
90      for (let i = 0; i < nebulaBlobs.length; i++) {
91        const blob = nebulaBlobs[i];
92
93        // Recompute gradient if missing (or after resize/regeneration)
94        if (!blob._gradient) {
95          const g = nebCtx.createRadialGradient(blob.x, blob.y, 0, blob.x, blob.y, blob.radius);
96          g.addColorStop(0, blob.color);
97          g.addColorStop(1, 'rgba(0,0,0,0)');
98          blob._gradient = g;
99        }
100
101        nebCtx.fillStyle = blob._gradient;
102        nebCtx.beginPath();
103        nebCtx.arc(blob.x, blob.y, blob.radius, 0, Math.PI * 2);
104        nebCtx.fill();
105
106        blob.x += blob.dx;
107        blob.y += blob.dy;
108
109        if (blob.x < -blob.radius) blob.x = W + blob.radius;
110        else if (blob.x > W + blob.radius) blob.x = -blob.radius;
111
112        if (blob.y < -blob.radius) blob.y = H + blob.radius;
113        else if (blob.y > H + blob.radius) blob.y = -blob.radius;
114      }
115    }
116
117    // ----------------- Starfield -----------------
118    let layers = [];
119    const TOTAL_LAYERS = 4;
120    const TOTAL_STARS = 6000;
121
122    function createStars() {
123      layers.length = 0;
124
125      for (let l = 0; l < TOTAL_LAYERS; l++) {
126        const layerStars = [];
127        const depthFactor = 1 + l;
128        const layerCount = TOTAL_STARS / TOTAL_LAYERS;
129
130        for (let i = 0; i < layerCount; i++) {
131          const clusterOffsetX = Math.random() < 0.02 ? (Math.random() - 0.5) * 50 : 0;
132          const clusterOffsetY = Math.random() < 0.02 ? (Math.random() - 0.5) * 50 : 0;
133
134          let baseColor =
135            l === 0 ? '#ffffff' :
136            l === 1 ? '#ffeedd' :
137            l === 2 ? '#fff8e0' : '#e0f0ff';
138
139          if (Math.random() < 0.01) baseColor = '#ff6666';
140
141          layerStars.push({
142            x: Math.random() * W + clusterOffsetX,
143            y: Math.random() * H + clusterOffsetY,
144            radius: (Math.random() ** 2.5) * 1.3 + 0.1,
145            color: baseColor,
146            rgb: hexToRgb(baseColor),
147            baseOpacity: Math.random() * 0.35 + 0.15,
148            twinkleSpeed: Math.random() * 0.6 + 0.1 - l * 0.05,
149            phase: Math.random() * Math.PI * 2,
150            depth: depthFactor,
151            burstPhase: -Math.random() * 5,
152            specialBurst: false,
153            burstDuration: 0,
154            nextTwinkle: Math.random() * 5 + 1,
155          });
156        }
157        layers.push(layerStars);
158      }
159    }
160
161    const shootingStars = [];
162
163    function createShootingStar() {
164      shootingStars.push({
165        x: Math.random() * W,
166        y: Math.random() * H * 0.5,
167        length: 80 + Math.random() * 40,
168        speed: 800 + Math.random() * 400,
169        angle: Math.random() * Math.PI / 6 + Math.PI / 12,
170        life: 0,
171      });
172    }
173
174    function drawStars(scrollOffset, t) {
175      starCtx.clearRect(0, 0, W, H);
176
177      for (let li = 0; li < layers.length; li++) {
178        const layer = layers[li];
179
180        for (let si = 0; si < layer.length; si++) {
181          const s = layer[si];
182
183          // Fast modulo
184          let y = s.y + scrollOffset / (s.depth * 10);
185          if (y >= H) y -= H;
186          else if (y < 0) y += H;
187
188          let opacity = s.baseOpacity + Math.sin(t * s.twinkleSpeed + s.phase) * 0.06;
189
190          if (!s.specialBurst && t > s.nextTwinkle) {
191            s.specialBurst = true;
192            s.burstPhase = t;
193            s.burstDuration = 0.5 + Math.random() * 0.5;
194            s.nextTwinkle = t + 3 + Math.random() * 5;
195          }
196
197          if (s.specialBurst) {
198            const dt = t - s.burstPhase;
199            if (dt < s.burstDuration) {
200              opacity += Math.sin((dt / s.burstDuration) * Math.PI) * 0.15;
201            } else {
202              s.specialBurst = false;
203            }
204          }
205
206          const shimmer = Math.sin((t - s.burstPhase) * 3) * Math.exp(-(t - s.burstPhase) * 1.2);
207          opacity += shimmer * 0.05;
208
209          const o = opacity < 0.1 ? 0.1 : opacity > 0.55 ? 0.55 : opacity;
210
211          starCtx.beginPath();
212          starCtx.arc(s.x, y, s.radius, 0, Math.PI * 2);
213          starCtx.fillStyle = `rgba(${s.rgb},${o})`;
214          starCtx.fill();
215        }
216      }
217
218      if (Math.random() < 0.002) createShootingStar();
219
220      for (let i = shootingStars.length - 1; i >= 0; i--) {
221        const st = shootingStars[i];
222        st.life += 0.016;
223
224        const dx = Math.cos(st.angle) * st.speed * 0.016;
225        const dy = Math.sin(st.angle) * st.speed * 0.016;
226
227        st.x += dx;
228        st.y += dy;
229
230        starCtx.beginPath();
231        starCtx.moveTo(st.x, st.y);
232        starCtx.lineTo(st.x - dx * 3, st.y - dy * 3);
233        starCtx.strokeStyle = 'rgba(255,255,255,0.8)';
234        starCtx.lineWidth = 1.5;
235        starCtx.stroke();
236
237        if (st.x > W || st.y > H) shootingStars.splice(i, 1);
238      }
239    }
240
241    function animate(time) {
242      const scrollOffset = window.scrollY || 0;
243      drawNebula();
244      drawStars(scrollOffset, time * 0.001);
245      requestAnimationFrame(animate);
246    }
247
248    createNebula();
249    createStars();
250    requestAnimationFrame(animate);
251
252    // WP Dark Mode: when toggled ON, force a resize + regenerate
253    new MutationObserver(() => {
254      if (document.documentElement.classList.contains('wp-dark-mode-active')) {
255        resizeCanvas();
256        createNebula();
257        createStars();
258      }
259    }).observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
260  });
261})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.