PageSourceSearch

https://jlabs.online/jlabs/main.js

js jlabs.online collected 2026-10-02 00:38:49 UTC 9,315 bytes, 304 lines download raw bytes

1import TubesCursor from "https://cdn.jsdelivr.net/npm/[email protected]/build/cursors/tubes1.min.js";
2import { initLanguageControls } from "./i18n.js";
3
4// Basit mobile menu ve dil toggle
5
6const menuToggle = document.getElementById("menuToggle");
7const mobileNav = document.getElementById("mobileNav");
8const menuClose = document.getElementById("menuClose");
9const mobileNavBackdrop = document.getElementById("mobileNavBackdrop");
10
11if (menuToggle && mobileNav && mobileNavBackdrop) {
12  const setMobileMenu = (isOpen) => {
13    mobileNav.classList.toggle("-translate-x-full", !isOpen);
14    mobileNav.classList.toggle("translate-x-0", isOpen);
15    mobileNavBackdrop.classList.toggle("opacity-0", !isOpen);
16    mobileNavBackdrop.classList.toggle("pointer-events-none", !isOpen);
17    menuToggle.setAttribute("aria-expanded", isOpen ? "true" : "false");
18    document.body.classList.toggle("overflow-hidden", isOpen);
19  };
20
21  const closeMobileMenu = () => setMobileMenu(false);
22
23  menuToggle.addEventListener("click", () => {
24    const isOpen = mobileNav.classList.contains("translate-x-0");
25    setMobileMenu(!isOpen);
26  });
27
28  if (menuClose) {
29    menuClose.addEventListener("click", closeMobileMenu);
30  }
31
32  mobileNavBackdrop.addEventListener("click", closeMobileMenu);
33
34  mobileNav.querySelectorAll("a").forEach((link) => {
35    link.addEventListener("click", closeMobileMenu);
36  });
37
38  window.addEventListener("resize", () => {
39    if (window.innerWidth >= 768) {
40      closeMobileMenu();
41    }
42  });
43}
44
45// Hero grid kareleri oluşturma (auto-fill grid, sabit kare boyut)
46const heroGrid = document.getElementById("heroGrid");
47let heroTiles = [];
48
49if (heroGrid) {
50  function populateGrid() {
51    heroGrid.innerHTML = "";
52    heroTiles = [];
53
54    const rect = heroGrid.getBoundingClientRect();
55    const style = getComputedStyle(heroGrid);
56    const gap = parseInt(style.gap) || 5;
57
58    // Ekran genişliğine göre karo boyutu
59    const w = window.innerWidth;
60    const tileSize = w >= 1024 ? 115 : w >= 768 ? 105 : 80;
61
62    const cols = Math.floor((rect.width + gap) / (tileSize + gap));
63    const rows = Math.floor((rect.height + gap) / (tileSize + gap));
64    const total = cols * rows;
65
66    heroGrid.style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
67    heroGrid.style.gridTemplateRows = `repeat(${rows}, 1fr)`;
68
69    // Section yüksekliğini tam grid + 1 karo boşluk olacak şekilde ayarla
70    const section = document.getElementById("landing");
71    if (section) {
72      const totalHeight = rows * tileSize + (rows - 1) * gap + tileSize;
73      section.style.minHeight = totalHeight + "px";
74    }
75
76    for (let i = 0; i < total; i++) {
77      const tile = document.createElement("div");
78      tile.className = "glass-element";
79      heroGrid.appendChild(tile);
80      heroTiles.push(tile);
81    }
82
83    return { cols, rows };
84  }
85
86  var gridInfo = populateGrid();
87
88  let resizeTimer;
89  window.addEventListener("resize", () => {
90    clearTimeout(resizeTimer);
91    resizeTimer = setTimeout(() => {
92      gridInfo = populateGrid();
93    }, 200);
94  });
95}
96
97// Hero mouse-follow animasyonu (cam karoları fare yakınında görünür yapma)
98const heroSection = document.getElementById("landing");
99
100// Masaüstünde etkileşimli, mobilde sadece arka plan animasyonu
101const heroInteractiveDesktop =
102  typeof window !== "undefined" &&
103  window.matchMedia &&
104  window.matchMedia("(pointer: fine) and (min-width: 768px)").matches;
105
106if (heroSection && heroGrid) {
107  const rect = heroSection.getBoundingClientRect();
108  let targetX = rect.width / 2;
109  let targetY = rect.height / 2;
110  let currentX = targetX;
111  let currentY = targetY;
112  let velocityX = 0;
113  let velocityY = 0;
114  const prevAlphas = [];
115  let cachedRect = heroSection.getBoundingClientRect();
116  let rectAge = 0;
117  let idleAngle = 0;
118  let heroFrame = 0;
119
120  if (heroInteractiveDesktop) {
121    heroSection.addEventListener("mousemove", (event) => {
122      const r = heroSection.getBoundingClientRect();
123      targetX = event.clientX - r.left;
124      targetY = event.clientY - r.top;
125    });
126
127    heroSection.addEventListener("mouseleave", () => {
128      const r = heroSection.getBoundingClientRect();
129      targetX = r.width * 0.15;
130      targetY = r.height / 2;
131    });
132  }
133
134  const animate = () => {
135    const spring = 0.14;
136    const friction = 0.8;
137
138    // Mobil / non-interactive modda hedefi idle bir yola bağla (sadece arka plan animasyonu)
139    if (!heroInteractiveDesktop) {
140      if (++rectAge > 60) {
141        cachedRect = heroSection.getBoundingClientRect();
142        rectAge = 0;
143      }
144      const r = cachedRect;
145      const centerX = r.width * 0.3;
146      const centerY = r.height * 0.5;
147      const size = Math.min(r.width, r.height) * 0.25;
148      const t = idleAngle;
149      targetX = centerX + size * Math.cos(t);
150      targetY = centerY + size * 0.6 * Math.sin(t);
151      idleAngle += 0.02;
152    }
153
154    velocityX += (targetX - currentX) * spring;
155    velocityY += (targetY - currentY) * spring;
156    velocityX *= friction;
157    velocityY *= friction;
158    currentX += velocityX;
159    currentY += velocityY;
160
161    // Mobilde karo opaklığı güncellemesini ~30fps'e düşür (DOM yazımı azalır)
162    heroFrame += 1;
163    const updateTileOpacity =
164      heroInteractiveDesktop || heroFrame % 2 === 0;
165
166    if (updateTileOpacity && heroTiles.length && gridInfo) {
167      // Rect'i periyodik olarak güncelle
168      if (heroInteractiveDesktop && ++rectAge > 30) {
169        cachedRect = heroSection.getBoundingClientRect();
170        rectAge = 0;
171      }
172
173      const cols = gridInfo.cols;
174      const blobCol = (currentX / cachedRect.width) * cols;
175      const blobRow = (currentY / cachedRect.height) * gridInfo.rows;
176      const radius = 2.8;
177      const invRadius = 1 / radius;
178
179      for (let i = 0, len = heroTiles.length; i < len; i++) {
180        const col = i % cols;
181        const row = (i - col) / cols;
182        const dx = col + 0.5 - blobCol;
183        const dy = row + 0.5 - blobRow;
184        const dist = Math.sqrt(dx * dx + dy * dy);
185
186        let alpha = 1 - dist * invRadius;
187        if (alpha < 0) alpha = 0;
188        else if (alpha > 1) alpha = 1;
189
190        // Quantize: sadece belirgin fark varsa DOM'u güncelle
191        const q = (alpha * 20 + 0.5) | 0;
192        if (q !== prevAlphas[i]) {
193          prevAlphas[i] = q;
194          heroTiles[i].style.opacity = (q / 20).toFixed(2);
195        }
196      }
197    }
198
199    requestAnimationFrame(animate);
200  };
201
202  animate();
203}
204
205// JLABS tubes cursor (WebGL) — mobilde kapat; contact/privacy ile aynı (iGPU / pil)
206const tubesCanvas = document.getElementById("tubesCanvas");
207const tubesRunWebGL =
208  tubesCanvas &&
209  typeof window !== "undefined" &&
210  window.matchMedia &&
211  window.matchMedia("(min-width: 768px)").matches;
212
213if (tubesRunWebGL) {
214  TubesCursor(tubesCanvas, {
215    tubes: {
216      colors: ["#FFE500", "#FFC300", "#FF8A00"],
217      lights: {
218        intensity: 120,
219        colors: ["#FFF7A5", "#FFE066", "#FFB347", "#FF7A1A"],
220      },
221    },
222  });
223
224  // Tek unified döngü: masaüstünde fareyi, mobilde sadece idle yolunu takip et
225  const landingSection = document.getElementById("landing");
226  if (landingSection) {
227    const tubesInteractiveDesktop =
228      typeof window !== "undefined" &&
229      window.matchMedia &&
230      window.matchMedia("(pointer: fine) and (min-width: 768px)").matches;
231
232    let mouseActive = false;
233    let mouseX = 0;
234    let mouseY = 0;
235    let smoothX = 0;
236    let smoothY = 0;
237    let idleAngle = 0;
238    let cachedRect = tubesCanvas.getBoundingClientRect();
239    let rectAge = 0;
240
241    // İlk pozisyonu idle merkezine ayarla
242    smoothX = cachedRect.left + cachedRect.width * 0.22;
243    smoothY = cachedRect.top + cachedRect.height * 0.5;
244
245    if (tubesInteractiveDesktop) {
246      landingSection.addEventListener("mousemove", (e) => {
247        mouseActive = true;
248        mouseX = e.clientX;
249        mouseY = e.clientY;
250      });
251
252      landingSection.addEventListener("mouseleave", () => {
253        mouseActive = false;
254      });
255    }
256
257    const tubesLoop = () => {
258      if (++rectAge > 90) {
259        cachedRect = tubesCanvas.getBoundingClientRect();
260        rectAge = 0;
261      }
262
263      let targetX;
264      let targetY;
265
266      if (tubesInteractiveDesktop && mouseActive) {
267        targetX = mouseX;
268        targetY = mouseY;
269      } else {
270        const r = cachedRect;
271        const centerX = r.left + r.width * 0.22;
272        const centerY = r.top + r.height * 0.5;
273        const size = Math.min(r.width, r.height) * 0.18;
274        const t = idleAngle;
275        const s = 1 + Math.sin(t) * Math.sin(t);
276        targetX = centerX + (size * 1.8 * Math.cos(t)) / s;
277        targetY = centerY + (size * 0.9 * Math.sin(t) * Math.cos(t)) / s;
278        idleAngle += 0.02;
279      }
280
281      // Masaüstünde fare varken daha agresif, diğer durumlarda yumuşak
282      const followStrength =
283        tubesInteractiveDesktop && mouseActive ? 0.18 : 0.06;
284      smoothX += (targetX - smoothX) * followStrength;
285      smoothY += (targetY - smoothY) * followStrength;
286
287      tubesCanvas.dispatchEvent(
288        new PointerEvent("pointermove", {
289          clientX: smoothX,
290          clientY: smoothY,
291          bubbles: true,
292        })
293      );
294
295      requestAnimationFrame(tubesLoop);
296    };
297
298    requestAnimationFrame(tubesLoop);
299  }
300}
301
302// Dil butonu + çeviri uygulama
303initLanguageControls();
304

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.