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.