1// ë©ì¸ ë©ë´ ë릴ë¤ì´ â Services / Plugin íì ë©ë´ 2function initMenuDrill() { 3 const menuNav = document.getElementById("menu-nav"); 4 const drillBtns = document.querySelectorAll( 5 ".menu-drill[data-menu-target]" 6 ); 7 const backBtns = document.querySelectorAll(".menu-back"); 8 const subPanels = document.querySelectorAll(".menu-sub-panel"); 9 10 if (!menuNav || !drillBtns.length || !backBtns.length) { 11 return null; 12 } 13 14 function setActivePanel(panelId) { 15 subPanels.forEach(function (panel) { 16 panel.classList.toggle("is-active", panel.id === panelId); 17 }); 18 } 19 20 function openSub(panelId, drillBtn) { 21 setActivePanel(panelId); 22 menuNav.classList.add("is-sub-open"); 23 drillBtns.forEach(function (btn) { 24 btn.setAttribute( 25 "aria-expanded", 26 btn === drillBtn ? "true" : "false" 27 ); 28 }); 29 } 30 31 function closeSub() { 32 menuNav.classList.remove("is-sub-open"); 33 drillBtns.forEach(function (btn) { 34 btn.setAttribute("aria-expanded", "false"); 35 }); 36 } 37 38 drillBtns.forEach(function (btn) { 39 btn.addEventListener("click", function () { 40 openSub(btn.getAttribute("data-menu-target"), btn); 41 }); 42 }); 43 backBtns.forEach(function (btn) { 44 btn.addEventListener("click", closeSub); 45 }); 46 47 return { updateViewportWidth: function () {} }; 48} 49 50// íì´í í¨ê³¼ í¨ì 51function typeWriter( 52 element, 53 text, 54 minSpeed = 80, 55 maxSpeed = 200, 56 onComplete = null 57) { 58 let i = 0; 59 60 // 먼ì 커ì ì¶ê° 61 const cursor = document.createElement("span"); 62 cursor.className = "cursor"; 63 element.appendChild(cursor); 64 65 function type() { 66 if (i < text.length) { 67 // íì¬ ì»¤ì ìì¹ì ê¸ì ì½ì 68 const textNode = document.createTextNode(text.charAt(i)); 69 element.insertBefore(textNode, cursor); 70 71 const currentChar = text.charAt(i); 72 i++; 73 74 // ì¬ëì²ë¼ ìì°ì¤ë¬ì´ íì´í ìë ê³ì° 75 let delay = Math.random() * (maxSpeed - minSpeed) + minSpeed; 76 77 // 10% íë¥ ë¡ ì§§ì ì ì§ (ìê°íë ìê°) 78 if (Math.random() < 0.1) { 79 delay += Math.random() * 200 + 100; // 100~300ms ì¶ê° 80 } 81 82 // 공백ì´ë 구ëì ë¤ììë ì½ê° ë 긴 ì ì§ 83 if (currentChar === " " || currentChar === "," || currentChar === ".") { 84 delay += Math.random() * 100 + 50; // 50~150ms ì¶ê° 85 } 86 87 // ëëë¡ ë¹ ë¥´ê² ì°ìì¼ë¡ íì´ííë í¨ê³¼ (30% íë¥ ) 88 if (Math.random() < 0.3) { 89 delay *= 0.5; // ì ë° ìëë¡ ë¹ ë¥´ê² 90 } 91 92 setTimeout(type, delay); 93 } else { 94 // íì´í ìë£ ì ì½ë°± í¨ì ì¤í 95 if (onComplete) { 96 onComplete(); 97 } 98 } 99 } 100 101 // 기존 í ì¤í¸ ì´ê¸°í (커ìë ì ì§) 102 element.innerHTML = ""; 103 element.appendChild(cursor); 104 type(); 105} 106 107// ë°±ì¤íì´ì¤ í¨ê³¼ í¨ì (í¹ì í ì¤í¸ë§ ìì ) 108function deleteText(element, deleteCount, speed = 80, onComplete = null) { 109 let i = 0; 110 const cursor = element.querySelector(".cursor"); 111 112 function deleteChar() { 113 if (i < deleteCount) { 114 // 커ì ë°ë¡ ìì í ì¤í¸ ë ¸ë ì°¾ìì ìì 115 const nodes = Array.from(element.childNodes).filter( 116 (node) => 117 node.nodeType === Node.TEXT_NODE && node.textContent.length > 0 118 ); 119 if (nodes.length > 0) { 120 const lastTextNode = nodes[nodes.length - 1]; 121 if (lastTextNode.textContent.length > 1) { 122 lastTextNode.textContent = lastTextNode.textContent.slice(0, -1); 123 } else { 124 element.removeChild(lastTextNode); 125 } 126 } 127 i++; 128 129 // ë°±ì¤íì´ì¤ë ìì°ì¤ë½ê² ìë ë³í 130 let delay = speed + Math.random() * 40 - 20; // ±20ms ë³ë 131 132 // 10% íë¥ ë¡ ì½ê° ë 긴 ì ì§ 133 if (Math.random() < 0.1) { 134 delay += Math.random() * 100 + 50; 135 } 136 137 // ê°ë ë¹ ë¥´ê² ì°ì ìì (20% íë¥ ) 138 if (Math.random() < 0.2) { 139 delay *= 0.6; 140 } 141 142 setTimeout(deleteChar, delay); 143 } else { 144 // ìì ìë£ ì ì½ë°± í¨ì ì¤í 145 if (onComplete) { 146 onComplete(); 147 } 148 } 149 } 150 151 deleteChar(); 152} 153 154// 기존 í ì¤í¸ì ì¶ê°ë¡ íì´ííë í¨ì 155function appendTypeWriter( 156 element, 157 text, 158 minSpeed = 80, 159 maxSpeed = 200, 160 onComplete = null 161) { 162 let i = 0; 163 const cursor = element.querySelector(".cursor"); 164 165 function type() { 166 if (i < text.length) { 167 const textNode = document.createTextNode(text.charAt(i)); 168 element.insertBefore(textNode, cursor); 169 170 const currentChar = text.charAt(i); 171 i++; 172 173 // ì¬ëì²ë¼ ìì°ì¤ë¬ì´ íì´í ìë ê³ì° 174 let delay = Math.random() * (maxSpeed - minSpeed) + minSpeed; 175 176 // 10% íë¥ ë¡ ì§§ì ì ì§ (ìê°íë ìê°) 177 if (Math.random() < 0.1) { 178 delay += Math.random() * 200 + 100; // 100~300ms ì¶ê° 179 } 180 181 // 공백ì´ë 구ëì ë¤ììë ì½ê° ë 긴 ì ì§ 182 if (currentChar === " " || currentChar === "," || currentChar === ".") { 183 delay += Math.random() * 100 + 50; // 50~150ms ì¶ê° 184 } 185 186 // ëëë¡ ë¹ ë¥´ê² ì°ìì¼ë¡ íì´ííë í¨ê³¼ (30% íë¥ ) 187 if (Math.random() < 0.3) { 188 delay *= 0.5; // ì ë° ìëë¡ ë¹ ë¥´ê² 189 } 190 191 setTimeout(type, delay); 192 } else { 193 if (onComplete) { 194 onComplete(); 195 } 196 } 197 } 198 199 type(); 200} 201 202// íì´ì§ ë¡ë ìë£ í íì´í í¨ê³¼ ìì 203document.addEventListener("DOMContentLoaded", function () { 204 const titleElement = document.getElementById("title"); 205 const titleText = "Synspear Dev Studio"; 206 const menuItems = document.querySelectorAll( 207 ".menu-main a, .menu-main .menu-drill" 208 ); 209 const langMenuItems = document.querySelectorAll(".menu-lang-item"); 210 const contentContainer = document.querySelector(".content"); 211 212 const menuDrill = initMenuDrill(); 213 214 // ë¹ëì¤ í¬ë¡ì¤ íì´ë í¨ê³¼ ì¤ì 215 const video1 = document.querySelector(".video-1"); 216 const video2 = document.querySelector(".video-2"); 217 218 if (video1 && video2) {
219 let currentVideo = video1; 220 let nextVideo = video2; 221 222 // íì¬ ë¹ëì¤ê° ëë기 1ì´ ì ë¶í° í¬ë¡ì¤ íì´ë ìì 223 function setupVideoTransition(current, next) { 224 current.addEventListener("timeupdate", function onTimeUpdate() { 225 const timeLeft = this.duration - this.currentTime; 226 227 if (timeLeft <= 1 && timeLeft > 0 && next.paused) { 228 // ë¤ì ë¹ëì¤ ìì ë° íì´ë ì¸ 229 next.currentTime = 0; 230 next.play(); 231 next.style.opacity = "1"; 232 233 // íì¬ ë¹ëì¤ íì´ë ìì 234 current.style.opacity = "0"; 235 236 // íì¬ ë¹ëì¤ê° ëëë©´ ì ì§ 237 current.addEventListener("ended", function onEnded() { 238 current.pause(); 239 current.removeEventListener("ended", onEnded); 240 }); 241 242 // ë¹ëì¤ ìí êµì²´ 243 setTimeout(() => { 244 const temp = currentVideo; 245 currentVideo = nextVideo; 246 nextVideo = temp; 247 setupVideoTransition(currentVideo, nextVideo); 248 }, 1000); 249 250 // ì´ë²¤í¸ 리ì¤ë ì ê±° (í ë²ë§ ì¤í) 251 current.removeEventListener("timeupdate", onTimeUpdate); 252 } 253 }); 254 } 255 256 setupVideoTransition(currentVideo, nextVideo); 257 } 258 259 // Dev Studioì ë¤ë¥¸ í ì¤í¸ë¤ì ìííë í¨ì 260 const alternateTexts = [ 261 "Developer", 262 "Engineer", 263 "Architect", 264 "Automator", 265 "Backend Lab", 266 "Logic Studio", 267 ]; 268 269 let currentTextIndex = 0; 270 271 function cycleTexts(index) { 272 if (index >= alternateTexts.length) { 273 // 모ë í ì¤í¸ ìí ìë£ í "Dev Studio" ë³µì 274 appendTypeWriter(titleElement, "Dev Studio", 50, 100, function () { 275 // 5ì´ ë기 í ë¤ì ì²ìë¶í° ìì 276 setTimeout(() => { 277 alternateText(); 278 }, 10000); 279 }); 280 return; 281 } 282 283 const currentText = alternateTexts[index]; 284 285 // íì¬ í ì¤í¸ íì´í 286 appendTypeWriter(titleElement, currentText, 50, 100, function () { 287 // 2ì´ ë기 í ìì 288 setTimeout(() => { 289 deleteText(titleElement, currentText.length, 60, function () { 290 // ë¤ì í ì¤í¸ë¡ ì§í 291 cycleTexts(index + 1); 292 }); 293 }, 2000); 294 }); 295 } 296 297 function alternateText() { 298 // "Dev Studio" ìì (10ê¸ì) 299 deleteText(titleElement, 10, 60, function () { 300 // ìí ìì 301 cycleTexts(0); 302 }); 303 } 304 305 // ë¤ë¡ê°ê¸°ë¡ ëìì¨ ê²½ì° ì²´í¬ 306 const isReturning = sessionStorage.getItem("returningHome"); 307 308 // ë©ì¸ ë©ë´ ë±ì¥ ì ëë©ì´ì 309 menuItems.forEach((item) => { 310 item.style.opacity = "0"; 311 item.style.transform = "translateY(20px)"; 312 item.style.transition = "opacity 0.6s ease, transform 0.6s ease"; 313 item.style.pointerEvents = "none"; 314 }); 315 316 langMenuItems.forEach((item) => { 317 item.addEventListener("mouseenter", function () { 318 if (!window.SiteLang) return; 319 const lang = window.SiteLang.getLang(); 320 const text = 321 lang === "ko" 322 ? this.getAttribute("data-kr") 323 : this.getAttribute("data-en"); 324 if (text) this.textContent = text; 325 }); 326 327 item.addEventListener("mouseleave", function () { 328 const english = this.getAttribute("data-en"); 329 if (english) this.textContent = english; 330 }); 331 }); 332 333 function resetMenuToEnglish() { 334 langMenuItems.forEach(function (item) { 335 const english = item.getAttribute("data-en"); 336 if (english) item.textContent = english; 337 }); 338 } 339 340 resetMenuToEnglish(); 341 window.addEventListener("sitelangchange", resetMenuToEnglish); 342 343 // ë¤ë¡ê°ê¸°ë¡ ëìì¨ ê²½ì° ì ëë©ì´ì ì¤íµ 344 if (isReturning === "true") { 345 sessionStorage.removeItem("returningHome"); 346 347 // transition ìì´ ì¦ì ìµì¢ ìì¹ë¡ (ì ëë©ì´ì 무ì) 348 contentContainer.style.transition = "none"; 349 contentContainer.classList.add("final-position"); 350 351 // íì´í ì¦ì íì 352 const cursor = document.createElement("span"); 353 cursor.className = "cursor"; 354 titleElement.textContent = titleText; 355 titleElement.appendChild(cursor); 356 357 // ë©ë´ ì¦ì íì (transition ì ê±°)
358 menuItems.forEach((item) => { 359 item.style.transition = "none"; 360 item.style.opacity = "1"; 361 item.style.transform = "translateY(0)"; 362 item.style.pointerEvents = "auto"; 363 }); 364 365 // ë¤ì íë ììì transition ë³µì (ì´í ì ëë©ì´ì ì ìí´) 366 requestAnimationFrame(() => { 367 contentContainer.style.transition = ""; 368 menuItems.forEach((item) => { 369 item.style.transition = "opacity 0.6s ease, transform 0.6s ease"; 370 }); 371 }); 372 373 if (menuDrill) { 374 menuDrill.updateViewportWidth(); 375 } 376 377 // ë°ë¡ êµì²´ ì ëë©ì´ì ìì 378 setTimeout(() => { 379 alternateText(); 380 }, 5000); 381 382 return; // ì´í íì´í ì ëë©ì´ì ì¤í ì í¨ 383 } 384 385 // 0.5ì´ ì§ì° í íì´í ìì 386 setTimeout(() => { 387 typeWriter(titleElement, titleText, 25, 125, function () { 388 // íì´í ìë£ í ë°ë¡ ì ì²´ ì½í ì¸ ë¥¼ ìµì¢ ìì¹ë¡ ì´ë 389 setTimeout(() => { 390 contentContainer.classList.add("final-position"); 391 392 // ì´ë ììê³¼ í¨ê» ìë¸ íì´íë¤ì´ ìì°¨ì ì¼ë¡ ëíëê² 393 menuItems.forEach((item, index) => { 394 setTimeout(() => { 395 item.style.opacity = "1"; 396 item.style.transform = "translateY(0)"; 397 398 // ë§ì§ë§ í¬í¸í´ë¦¬ì¤ ë©ë´ ì ëë©ì´ì ìë£ í í´ë¦ íì±í 399 if (index === menuItems.length - 1) { 400 setTimeout(() => { 401 menuItems.forEach((menuItem) => { 402 menuItem.style.pointerEvents = "auto"; 403 }); 404 if (menuDrill) { 405 menuDrill.updateViewportWidth(); 406 } 407 }, 600); 408 } 409 }, index * 150); 410 }); 411 412 // ìë¸ íì´í ì ëë©ì´ì ìë£ í 5ì´ ë기íê³ êµì²´ ì ëë©ì´ì ìì 413 const lastMenuDelay = (menuItems.length - 1) * 150 + 600; // ë§ì§ë§ ë©ë´ ì ëë©ì´ì ìë£ ìê° 414 setTimeout(() => { 415 alternateText(); 416 }, lastMenuDelay + 5000); // 5ì´ ì¶ê° ë기 417 }, 100); 418 }); 419 }, 500); 420 421 // ì½í í¸ í¼ ì²ë¦¬ 422 const contactForm = document.querySelector(".contact-form"); 423 console.log("[DEBUG] Contact form found:", contactForm); 424 if (contactForm) { 425 contactForm.addEventListener("submit", async function (e) { 426 console.log("[DEBUG] Form submit event triggered"); 427 e.preventDefault(); 428 429 // í¼ ë°ì´í° ìì§ 430 const formData = { 431 company_name: document.getElementById("companyName").value, 432 contact_name: document.getElementById("contactName").value, 433 email: document.getElementById("emailAddress").value, 434 phone_number: document.getElementById("phoneNumber").value, 435 project_type: document.getElementById("projectType").value, 436 budget_range: document.getElementById("budgetRange").value, 437 timeline: document.getElementById("timeline").value, 438 project_description: 439 document.getElementById("projectDescription").value, 440 type: "production_inquiry", // ì ì문ì 441 }; 442 443 console.log("[DEBUG] Form data collected:", formData); 444 445 // íì íë ê²ì¦ 446 if ( 447 !formData.company_name || 448 !formData.contact_name || 449 !formData.email || 450 !formData.project_description 451 ) { 452 alert("íì í목ì 모ë ì ë ¥í´ì£¼ì¸ì."); 453 return; 454 } 455 456 // ì´ë©ì¼ íì ê²ì¦ 457 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; 458 if (!emailRegex.test(formData.email)) { 459 alert("ì¬ë°ë¥¸ ì´ë©ì¼ 주ì를 ì ë ¥í´ì£¼ì¸ì."); 460 return; 461 } 462 463 // ì ì¶ ë²í¼ ë¹íì±í ë° ë¡ë© íì 464 const submitButton = contactForm.querySelector('button[type="submit"]'); 465 const originalText = submitButton.innerHTML; 466 submitButton.disabled = true; 467 submitButton.innerHTML = 468 '<span class="english">Sending...</span><span class="korean">ì ì¡ ì¤...</span>'; 469 470 try { 471 const response = await fetch("/api/contact-submit", { 472 method: "POST", 473 headers: { 474 "Content-Type": "application/json", 475 }, 476 body: JSON.stringify(formData), 477 }); 478 479 const result = await response.json(); 480 481 if (result.success) { 482 alert( 483 "문ìê° ì±ê³µì ì¼ë¡ ì ì¡ëììµëë¤. ë¹ ë¥¸ ìì¼ ë´ì ì°ë½ëë¦¬ê² ìµëë¤." 484 ); 485 contactForm.reset(); // í¼ ì´ê¸°í 486 } else { 487 alert("문ì ì ì¡ì ì¤í¨íìµëë¤: " + result.message); 488 } 489 } catch (error) { 490 console.error("Error:", error); 491 alert("문ì ì ì¡ ì¤ ì¤ë¥ê° ë°ìíìµëë¤. ì ì í ë¤ì ìëí´ì£¼ì¸ì."); 492 } finally { 493 // ì ì¶ ë²í¼ ë³µì 494 submitButton.disabled = false;
495 submitButton.innerHTML = originalText; 496 } 497 }); 498 } 499});
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.