console.log('✅ main script file loaded'); /**************************************************** * ✅ ·¹°Å½Ã À¯Æ¿ (bluring / getUrl / goLocate) ****************************************************/ function bluring() { try { if (event.srcElement.tagName === 'A' || event.srcElement.tagName === 'IMG') { document.body.focus(); } } catch (e) {} } try { document.onfocusin = bluring; } catch (e) {} var rurl = location.href; var purl = getUrl(rurl); function getUrl(url_str) { var real_url; if (url_str.indexOf('/') > 0) { real_url = url_str.split('/'); real_url = real_url[0] + '//' + real_url[2] + '/' + real_url[3] + '/'; } return real_url; } function goLocate(go_url) { document.location = purl + go_url; } /**************************************************** * ✅ °í°´»ç ·Î°í ¹«ÇÑ ·Ñ¸µ ****************************************************/ function initCustomerLogoMarquee() { const viewport = document.querySelector('#customer .customer-logos'); if (!viewport) return; const items = Array.from(viewport.querySelectorAll('.logo-item')); if (items.length === 0) return; if (viewport.querySelector('.customer-logos-track')) return; const track = document.createElement('div'); track.className = 'customer-logos-track'; items.forEach((el) => track.appendChild(el)); viewport.appendChild(track); items.forEach((el) => { const clone = el.cloneNode(true); clone.classList.add('logo-item-clone'); track.appendChild(clone); }); let running = true; const pxPerSec = 60; let lastTs = null; let x = 0; let oneSetWidth = 0; function refreshWidth() { oneSetWidth = track.scrollWidth / 2; if (!oneSetWidth) requestAnimationFrame(refreshWidth); } window.addEventListener('load', refreshWidth); refreshWidth(); function step(ts) { if (!running) return; if (lastTs == null) lastTs = ts; const dt = (ts - lastTs) / 1000; lastTs = ts; if (oneSetWidth > 0) { x -= pxPerSec * dt; if (Math.abs(x) >= oneSetWidth) x += oneSetWidth; track.style.transform = `translate3d(${x}px, 0, 0)`; } requestAnimationFrame(step); } requestAnimationFrame(step); document.addEventListener('visibilitychange', () => { if (document.hidden) { running = false; } else { running = true; lastTs = null; requestAnimationFrame(step); } }); window.addEventListener('resize', refreshWidth); } /**************************************************** * ✅ DOMContentLoaded ****************************************************/ document.addEventListener('DOMContentLoaded', function () { console.log('✅ DOMContentLoaded fired'); const mobileMenuToggle = document.getElementById('mobileMenuToggle'); const mainMenu = document.getElementById('mainMenu'); if (mobileMenuToggle && mainMenu) { mobileMenuToggle.addEventListener('click', function () { mainMenu.classList.toggle('mobile-menu-open'); }); const menuItems = mainMenu.querySelectorAll('.navbar_items'); menuItems.forEach(function (item) { item.addEventListener('click', function () { mainMenu.classList.remove('mobile-menu-open'); }); }); } const scrollLinks = document.querySelectorAll('.js-scroll'); scrollLinks.forEach((link) => { link.addEventListener('click', function (e) { const targetId = this.getAttribute('href'); if (!targetId || !targetId.startsWith('#')) return; const targetEl = document.querySelector(targetId); if (!targetEl) return; e.preventDefault(); const topMenu = document.querySelector('#topmenu'); const topMenuHeight = topMenu ? topMenu.offsetHeight : 0; window.scrollTo({ top: targetEl.offsetTop - topMenuHeight, behavior: 'smooth' }); }); }); const topMenu = document.querySelector('#topmenu'); const navItems = document.querySelectorAll('.navbar_items'); function updateTopMenuAndActive() { if (!topMenu) return; const currentScroll = window.pageYOffset; const topMenuHeight = topMenu.offsetHeight; topMenu.style.background = 'rgba(255,255,255,0.98)'; topMenu.style.boxShadow = currentScroll > 100 ? '0 4px 20px rgba(0,0,0,0.1)' : '0 2px 10px rgba(0,0,0,0.05)'; const sections = document.querySelectorAll('section[id]'); let current = ''; sections.forEach((section) => { const isContact = section.id === 'contact-section'; const offset = isContact ? 200 : 100; const sectionTop = section.offsetTop - topMenuHeight - offset; const sectionBottom = sectionTop + section.clientHeight; if (currentScroll >= sectionTop && currentScroll < sectionBottom) { current = section.id; } }); navItems.forEach((item) => { item.classList.remove('active'); const href = item.getAttribute('href'); if (current === 'contact-section' && href === '#contact') { item.classList.add('active'); return; } if (href === '#' + current) item.classList.add('active'); }); } window.addEventListener('scroll', updateTopMenuAndActive); updateTopMenuAndActive(); initCustomerLogoMarquee(); }); /**************************************************** * ✅ °Ô½ÃÆÇ ID ¼³Á¤ * ⚠️ ½ÇÁ¦ Ä«Æä24 °Ô½ÃÆÇ ¹øÈ£¿Í ¸Â´ÂÁö È®ÀÎÇØÁÖ¼¼¿ä ****************************************************/ var BOARD_ID = { PORTFOLIO: '8', // Æ÷Æ®Æú¸®¿À °Ô½ÃÆÇ ¡æ Ä«Å×°í¸® AJAX + ¸ð´Þ BLOG: '10', // ºí·Î±× °Ô½ÃÆÇ ¡æ »ó¼¼ÆäÀÌÁö À̵¿ }; function getBoardId(url) { try { const u = new URL(String(url || '').replace(/&/g, '&'), location.href); return u.searchParams.get('com_board_id') || ''; } catch { return ''; } } function normalizeUrl(url) { const raw = String(url || '').replace(/&/g, '&'); try { return new URL(raw, location.href).toString(); } catch { return raw; } } /**************************************************** * ✅ Æ÷Æ®Æú¸®¿À (Ä«Å×°í¸®/ÆäÀÌ¡ AJAX ±³Ã¼ + ¸ð´Þ) ****************************************************/ (function () { function extractUrlFromOnclick(el) { const onclick = el && el.getAttribute && el.getAttribute('onclick'); if (!onclick) return null; const m = onclick.match(/linkMove\('([^']+)'\)/); return m ? m[1] : null; } async function fetchHtml(url) { const res = await fetch(url, { credentials: 'same-origin' }); const buf = await res.arrayBuffer(); let text = new TextDecoder('euc-kr').decode(buf); const m = text.match(/charset\s*=\s*["']?([a-zA-Z0-9-_]+)["']?/i); const enc = m && m[1] ? m[1].toLowerCase() : ''; if (enc.includes('utf')) text = new TextDecoder('utf-8').decode(buf); return text; } function findListContainer(root) { return ( root.querySelector('#portfolio #container_list') || root.querySelector('#portfolio [id^="container_list"]') || root.querySelector('#container_list') || root.querySelector('[id^="container_list"]') || root.querySelector('#portfolio .portfolio-board') || root.querySelector('.portfolio-board') ); } function getPortfolioAnchorOpts() { const portfolioSection = document.querySelector('#portfolio'); const header = document.querySelector('#topmenu'); const headerH = header ? header.offsetHeight : 0; return { anchorEl: portfolioSection, offset: -headerH - 20 }; } async function replaceBoardList(url, opts = {}) { const { anchorEl = null, offset = 0 } = opts; // offsetTop: ÆäÀÌÁö ÃÖ»ó´Ü ±âÁØ Àý´ë À§Ä¡ (½ºÅ©·Ñ ¿µÇâ ¾øÀ½) const anchorY = anchorEl ? anchorEl.offsetTop + offset : window.scrollY; console.log('[portfolio] anchorY:', anchorY, '| offsetTop:', anchorEl?.offsetTop, '| offset:', offset); const html = await fetchHtml(url); const doc = new DOMParser().parseFromString(html, 'text/html'); const newEl = findListContainer(doc); const curEl = findListContainer(document); if (!newEl || !curEl) { location.href = url; return; } curEl.innerHTML = newEl.innerHTML; history.replaceState(null, '', url); window.scrollTo({ top: anchorY, behavior: 'instant' }); console.log('[portfolio] scrollTo ¿Ï·á:', anchorY); } // Ä«Å×°í¸® Ŭ¸¯: #portfolio ³»ºÎ + board_id=PORTFOLIO¸¸ async function onCategoryClick(e) { if (!e.target.closest('#portfolio')) return; // #category_navi ³»ºÎ Ŭ¸¯ÀÎÁö È®ÀÎ const navi = e.target.closest('#category_navi'); if (!navi) return; // onclick="linkMove(...)" ¹æ½Ä const tab = e.target.closest('#category_navi > div'); let url = null; if (tab) { const urlRaw = extractUrlFromOnclick(tab); if (urlRaw) url = normalizeUrl(urlRaw); } // href ¹æ½Ä (a ű×) if (!url) { const a = e.target.closest('a[href]'); if (a) { const href = a.getAttribute('href'); if (href && !href.startsWith('javascript:')) url = normalizeUrl(href); } } // onclick ¼Ó¼º Á÷Á¢ ÆÄ½Ì (¾î¶² ¿ä¼Òµç) if (!url) { const onclickEl = e.target.closest('[onclick]'); if (onclickEl) { const urlRaw = extractUrlFromOnclick(onclickEl); if (urlRaw) url = normalizeUrl(urlRaw); } } if (!url) { console.log('[cat] ❌ urlÀ» ãÁö ¸øÇÔ'); return; } console.log('[cat] url:', url, '| boardId:', getBoardId(url)); // com_board_category_code°¡ ÀÖÀ¸¸é Æ÷Æ®Æú¸®¿À Ä«Å×°í¸® ¿äûÀ¸·Î °£ÁÖ const hasCategory = url.includes('com_board_category_code'); const boardIdMatch = getBoardId(url) === BOARD_ID.PORTFOLIO; // board_id°¡ ¾ø´Â Ä«Å×°í¸® URLÀ̸é ÇöÀç ÆäÀÌÁö board_id ±âÁØÀ¸·Î ÆÇ´Ü if (!boardIdMatch && !hasCategory) { console.log('[cat] ❌ portfolio ¾Æ´Ô'); return; } e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); console.log('[cat] ✅ replaceBoardList È£Ãâ, url:', url); try { await replaceBoardList(url, getPortfolioAnchorOpts()); } catch (err) { console.error(err); location.href = url; } } // ÆäÀÌ¡ Ŭ¸¯: #portfolio ³»ºÎ + board_id=PORTFOLIO¸¸ async function onPagingClick(e) { if (!e.target.closest('#portfolio')) return; const a = e.target.closest('#ext_paging a'); if (!a) return; const href = a.getAttribute('href'); if (!href || href.startsWith('javascript:')) return; const url = normalizeUrl(href); if (getBoardId(url) !== BOARD_ID.PORTFOLIO) return; e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); try { await replaceBoardList(url, getPortfolioAnchorOpts()); } catch (err) { console.error(err); location.href = url; } } function attachPortfolioEvents() { const portfolioSection = document.querySelector('#portfolio'); if (!portfolioSection) return; ['mousedown', 'touchstart', 'click'].forEach((evt) => { portfolioSection.addEventListener(evt, onCategoryClick, true); portfolioSection.addEventListener(evt, onPagingClick, true); }); } // linkMove Àü¿ª °¡·Îä±â function lockedLinkMove(url) { const normalized = normalizeUrl(url); const boardId = getBoardId(normalized); console.log('[linkMove] url:', normalized, '| boardId:', boardId); // Æ÷Æ®Æú¸®¿À ÆÇ´Ü: // 1) com_board_id=8 ¸í½Ã // 2) com_board_id ¾øÀÌ com_board_category_code¸¸ ÀÖ´Â °æ¿ì // ¡æ ÇöÀç #portfolio ¼½¼ÇÀÌ ÆäÀÌÁö¿¡ ÀÖÀ¸¸é Æ÷Æ®Æú¸®¿À Ä«Å×°í¸® ¿äûÀ¸·Î °£ÁÖ const isPortfolio = boardId === BOARD_ID.PORTFOLIO || (!boardId && !!document.querySelector('#portfolio #category_navi')); if (isPortfolio) { if (normalized.includes('com_board_basic=read_form')) { // »ó¼¼´Â ¸ð´Þ¿¡¼­ ó¸® console.log('[linkMove] Æ÷Æ®Æú¸®¿À »ó¼¼ ¡æ ¸ð´Þ ó¸®'); return false; } console.log('[linkMove] ✅ Æ÷Æ®Æú¸®¿À AJAX ±³Ã¼'); replaceBoardList(normalized, getPortfolioAnchorOpts()).catch((err) => { console.error(err); location.href = normalized; }); return false; } // ºí·Î±× ¹× ±âŸ ¡æ ÆäÀÌÁö À̵¿ console.log('[linkMove] ÀÏ¹Ý ÆäÀÌÁö À̵¿'); location.href = normalized; return false; } // writable:false + configurable:false·Î µ¤¾î¾²±â ¿ÏÀü Â÷´Ü function installLinkMove() { try { Object.defineProperty(window, 'linkMove', { get: function () { return lockedLinkMove; }, set: function () { /* µ¤¾î¾²±â Â÷´Ü */ }, configurable: false, }); } catch (e) { window.linkMove = lockedLinkMove; } } installLinkMove(); // board.js°¡ ³ªÁß¿¡ ·ÎµåµÇ¾î µ¤¾î½áµµ º¹±¸ // script ÅÂ±× Ãß°¡ °¨Áö ½Ã linkMove ÀçÈ®ÀÎ const _observer = new MutationObserver(function () { if (window.linkMove !== lockedLinkMove) { try { Object.defineProperty(window, 'linkMove', { get: function () { return lockedLinkMove; }, set: function () { }, configurable: false, }); } catch (e) { window.linkMove = lockedLinkMove; } } }); _observer.observe(document.head || document.documentElement, { childList: true, subtree: true }); // Æ÷Æ®Æú¸®¿À ¸ð´Þ function ensureModal() { let overlay = document.querySelector('.pf-modal-overlay'); if (overlay) return overlay; overlay = document.createElement('div'); overlay.className = 'pf-modal-overlay'; overlay.innerHTML = ` `; document.body.appendChild(overlay); overlay.addEventListener('click', (e) => { if (e.target === overlay) closeModal(); }); overlay.querySelector('.pf-modal-close').addEventListener('click', closeModal); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); }); return overlay; } function openModal({ imgSrc, title }) { const overlay = ensureModal(); overlay.querySelector('.pf-modal-img').src = imgSrc || ''; overlay.querySelector('.pf-modal-title').textContent = title || ''; overlay.style.display = 'flex'; document.documentElement.style.overflow = 'hidden'; } function closeModal() { const overlay = document.querySelector('.pf-modal-overlay'); if (!overlay) return; overlay.style.display = 'none'; document.documentElement.style.overflow = ''; } // ½æ³×ÀÏ Å¬¸¯ ¡æ ¸ð´Þ (#portfolio ³»ºÎ¸¸) document.addEventListener('click', function (e) { if (!e.target.closest('#portfolio')) return; const a = e.target.closest('.gallery_item_table .item_cell_media a'); if (!a) return; e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); const table = a.closest('.gallery_item_table'); const imgEl = a.querySelector('img'); const titleEl = table?.querySelector('.item_cell_subject span'); const title = (titleEl?.textContent || '').replace(/\s+/g, ' ').trim(); let imgSrc = imgEl?.getAttribute('src') || ''; if (imgSrc && !imgSrc.startsWith('http')) imgSrc = new URL(imgSrc, location.href).toString(); openModal({ imgSrc, title }); }, true); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', attachPortfolioEvents); } else { attachPortfolioEvents(); } })(); /**************************************************** * ✅ ºí·Î±× °Ô½ÃÆÇ - »ó¼¼ÆäÀÌÁö À̵¿ º¸Àå * Ä«Æä24 °¶·¯¸®Çü °Ô½ÃÆÇÀº onclick="linkMove(...)"·Î ·»´õ¸µµÇ¾î * °¡ ºñ¾îÀÖ´Â °æ¿ì°¡ ÀÖÀ¸¹Ç·Î ¸í½ÃÀûÀ¸·Î ó¸® ****************************************************/ (function () { document.addEventListener('click', function (e) { // ºí·Î±× ¼½¼Ç ³»ºÎ¸¸ if (!e.target.closest('#blog')) return; // href°¡ Á¤»óÀÎ ¸µÅ©´Â ±âº» µ¿ÀÛ(ÆäÀÌÁö À̵¿) Çã¿ë const a = e.target.closest('a[href]'); if (a) { const href = a.getAttribute('href'); if (href && !href.startsWith('javascript:') && href !== '#') return; } // onclick="linkMove(...)" ó¸® const clickedEl = e.target.closest('[onclick]'); if (!clickedEl) return; const onclick = clickedEl.getAttribute('onclick') || ''; const m = onclick.match(/linkMove\('([^']+)'\)/); if (!m) return; e.preventDefault(); e.stopImmediatePropagation(); e.stopPropagation(); location.href = normalizeUrl(m[1]); }, true); })(); /**************************************************** * ✅ Achievement ¼ýÀÚ Ä«¿îÆ® ¾Ö´Ï¸ÞÀÌ¼Ç ****************************************************/ (function () { const intFormatter = new Intl.NumberFormat('ko-KR'); function formatInt(value) { return intFormatter.format(Math.floor(Number(value) || 0)); } function getOrCreateNumberTextNode(numberEl) { const suffixSpan = numberEl.querySelector('span'); if (!suffixSpan) return null; if (numberEl.lastChild !== suffixSpan) numberEl.appendChild(suffixSpan); let numNode = null; Array.from(numberEl.childNodes).forEach((node) => { if (node === suffixSpan) return; if (node.nodeType === Node.TEXT_NODE) { const v = node.nodeValue || ''; if (/\d/.test(v)) { if (!numNode) numNode = node; else node.remove(); } else { node.remove(); } } else { node.remove(); } }); if (!numNode) { numNode = document.createTextNode(''); numberEl.insertBefore(numNode, suffixSpan); } return numNode; } function animateCounter(numberEl, target, isDecimal, duration = 3000) { let start = 0; const increment = target / (duration / 16); const suffixSpan = numberEl.querySelector('span'); const numberTextNode = suffixSpan ? getOrCreateNumberTextNode(numberEl) : null; const timer = setInterval(() => { start += increment; const val = Math.min(start, target); const displayText = isDecimal ? Number(val).toFixed(2) : formatInt(val); if (numberTextNode) numberTextNode.nodeValue = displayText + ' '; else numberEl.textContent = displayText; if (start >= target) { if (!isDecimal) { if (numberTextNode) numberTextNode.nodeValue = formatInt(target) + ' '; else numberEl.textContent = formatInt(target); } clearInterval(timer); } }, 16); } function checkAchievementSection() { const achievementSection = document.querySelector('.achievement-section'); if (!achievementSection) return; const achievementNumbers = document.querySelectorAll('.achievement-number'); if (achievementNumbers.length === 0) return; const sectionTop = achievementSection.offsetTop; const scrollPosition = window.pageYOffset + window.innerHeight; if (scrollPosition > sectionTop && !achievementSection.classList.contains('counted')) { achievementSection.classList.add('counted'); achievementNumbers.forEach((numberEl) => { const text = (numberEl.textContent || '').trim(); const target = parseFloat(text.replace(/[^0-9.]/g, '')); const isDecimal = text.includes('.') || target % 1 !== 0; const suffixSpan = numberEl.querySelector('span'); if (suffixSpan) { const tn = getOrCreateNumberTextNode(numberEl); tn.nodeValue = isDecimal ? '0.00 ' : '0 '; } else { numberEl.textContent = isDecimal ? '0.00' : '0'; } setTimeout(() => animateCounter(numberEl, target, isDecimal, 3000), 100); }); } } window.addEventListener('scroll', checkAchievementSection); checkAchievementSection(); })(); /**************************************************** * ✅ ScrollSpy (IntersectionObserver) * ScrollMagicÀº React DevTools ȯ°æ¿¡¼­ rAF Ãæµ¹·Î Á¦°Å ****************************************************/ (function () { function initScrollSpy() { const spyEls = document.querySelectorAll('section.scroll-spy'); const observer = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { if (entry.isIntersecting) entry.target.classList.add('show'); }); }, { threshold: 0.2 } ); spyEls.forEach(function (spyEl) { observer.observe(spyEl); }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { setTimeout(initScrollSpy, 100); }); } else { setTimeout(initScrollSpy, 100); } })();