1// DOMContentLoaded ì´íì ë¡ëëë ê²½ì°(ëì include)ë ì¦ì ì¤íëëë¡ ë³´ì¡° í¨ì 2function onReady(fn) { 3 if (document.readyState === 'loading') { 4 document.addEventListener('DOMContentLoaded', fn, { once: true }); 5 } else { 6 fn(); 7 } 8} 9 10// -------------------------------------------------ì¤í¬ë¡¤ ì ì ëë©ì´ì ì¤í 11onReady(() => { 12 const boxes = document.querySelectorAll('.animation'); 13 // ë·°í¬í¸ ëë¹ì ë°ë¼ threshold ì¡°ì 14 const thresholdValue = window.innerWidth <= 767 ? 0.3 : 0.3; 15 const observer = new IntersectionObserver( 16 (entries, observer) => { 17 entries.forEach((entry) => { 18 if (entry.isIntersecting) { 19 entry.target.classList.add('show'); 20 observer.unobserve(entry.target); // ì ëë©ì´ì íë²ë§ ì¤í 21 } 22 }); 23 }, 24 { 25 threshold: thresholdValue, 26 }, 27 ); 28 boxes.forEach((box) => observer.observe(box)); 29}); 30 31//-----------------------------------------------------------í¤ëê³ ì 32onReady(function () { 33 const header = document.querySelector('#header'); 34 if (!header) return; // #header ìì¼ë©´ ì¢ ë£ (ìë¬ ë°©ì§) 35 36 window.addEventListener('scroll', function () { 37 header.classList.toggle('on', window.scrollY >= 100); 38 }); 39}); 40 41//-------------------------------------------------í¤ë ìë¸ë©ë´ ì´ê³ ë«ê¸° (íì í) 42onReady(() => { 43 const nav = document.querySelector('#nav'); 44 if (!nav) return; 45 46 const menus = nav.querySelectorAll('.sub_open'); 47 48 const closeAll = () => { 49 nav.querySelectorAll('.sub.on').forEach((el) => el.classList.remove('on')); 50 nav.querySelectorAll('.sub_open.active').forEach((el) => el.classList.remove('active')); 51 }; 52 53 menus.forEach((menu) => { 54 const sub = menu.querySelector('.sub'); 55 if (!sub) return; 56 57 // ë§ì°ì¤ ì¬ë¦¬ë©´ ì´ê¸° 58 menu.addEventListener('mouseenter', () => { 59 closeAll(); 60 sub.classList.add('on'); 61 menu.classList.add('active'); 62 }); 63 64 // ë©ë´ + ìë¸ë©ë´ ìììì ë§ì°ì¤ ë¹ ì§ë©´ ë«ê¸° 65 menu.addEventListener('mouseleave', () => { 66 sub.classList.remove('on'); 67 menu.classList.remove('active'); 68 }); 69 }); 70}); 71 72//-------------------------------------------------맨 ìë¡ 73function goTop() { 74 $(window).scrollTop(0); 75} 76 77//-------------------------------------------------íë¡í ë°°ë 78onReady(() => { 79 const floating = document.querySelector('.floating'); 80 const footer = document.querySelector('footer'); // #footer ì°ìë©´ 거기ì ë§ê² ë°ê¾¸ì ë ë©ëë¤. 81 82 if (!floating || !footer) return; // í¹ì ìì ìì¼ë©´ ì¡°ì©í ì¢ ë£ 83 84 window.addEventListener('scroll', () => { 85 const scrollY = window.scrollY || window.pageYOffset; 86 const footerRect = footer.getBoundingClientRect(); 87 const windowH = window.innerHeight; 88 89 // footerê° íë©´ ìì ë³´ì´ëì§ ì²´í¬ 90 const footerInView = footerRect.top <= windowH && footerRect.bottom >= 0; 91 92 // ì¡°ê±´: ì¤í¬ë¡¤ 500 ì´ì & footer ì ë³´ì¼ ëë§ .on 93 if (scrollY >= 500 && !footerInView) { 94 floating.classList.add('on'); 95 } else { 96 floating.classList.remove('on'); 97 } 98 }); 99}); 100 101//-------------------------------------------------모ë°ì¼ ë©ë´ ì´ê³ ë«ê¸° 102onReady(function () { 103 const menuBtn = document.querySelector('#header_m .menu_btn'); // ìë¨ ë©ë´ ë²í¼ 104 const mMenu = document.getElementById('m_menu'); // ì ì²´ ì¤ë²ë ì´ 105 106 // íì ììê° ìì¼ë©´ ì¢ ë£ (íì´ì§ë§ë¤ í¤ë/ë©ë´ê° ìì ì ìì) 107 if (!menuBtn || !mMenu) return; 108 109 const mMenuInner = mMenu.querySelector('.inner'); // ì쪽 ë°ì¤ 110 const closeBtn = mMenu.querySelector('.close'); // ë«ê¸° ë²í¼ 111 112 // 1) ë©ë´ ë²í¼ í´ë¦ -> ì´ê¸° 113 menuBtn.addEventListener('click', function (e) { 114 e.preventDefault(); 115 mMenu.classList.add('open'); 116 }); 117 118 // 2) ì¤ë²ë ì´ ë°°ê²½ í´ë¦ -> ë«ê¸° 119 mMenu.addEventListener('click', function () { 120 mMenu.classList.remove('open'); 121 }); 122 123 // 3) ë´ë¶ í´ë¦ -> ë«íì§ ìê² 124 if (mMenuInner) { 125 mMenuInner.addEventListener('click', function (e) { 126 e.stopPropagation(); 127 }); 128 } 129 130 // 4) ë«ê¸° ë²í¼ í´ë¦ -> ë«ê¸° 131 if (closeBtn) { 132 closeBtn.addEventListener('click', function (e) { 133 e.preventDefault(); 134 mMenu.classList.remove('open'); 135 }); 136 } 137}); 138 139//-------------------------------------------------ì¬ì´í¸ íì 140onReady(() => { 141 if (window.__sitePopupInit) return; 142 window.__sitePopupInit = true; 143 if (/\/login(\.html)?$/.test(location.pathname)) return; 144 145 function escapeHtml(value) { 146 return String(value ?? '') 147 .replace(/&/g, '&') 148 .replace(/</g, '<') 149 .replace(/>/g, '>') 150 .replace(/"/g, '"') 151 .replace(/'/g, '''); 152 } 153 154 function normalizePath(value) { 155 let p = String(value || '').trim(); 156 if (!p) return '/'; 157 try { 158 const u = new URL(p, location.origin); 159 p = u.pathname || '/'; 160 } catch {} 161 if (!p.startsWith('/')) p = '/' + p; 162 if (p.length > 1 && p.endsWith('/')) p = p.slice(0, -1); 163 if (p === '/index.html') return '/'; 164 return p || '/'; 165 } 166 167 function hideKey(item) { 168 return `site-popup-hide-until:${item.id}`; 169 } 170 171 function isHidden(item) { 172 try { 173 const raw = localStorage.getItem(hideKey(item)); 174 if (!raw) return false;
175 const until = Number(raw); 176 return Number.isFinite(until) && until > Date.now(); 177 } catch { 178 return false; 179 } 180 } 181 182 function setHidden(item, days) { 183 try { 184 const until = Date.now() + Math.max(1, Number(days) || 1) * 24 * 60 * 60 * 1000; 185 localStorage.setItem(hideKey(item), String(until)); 186 } catch {} 187 } 188 189 function createLayer(item) { 190 const layer = document.createElement('div'); 191 layer.className = 'site-popup-layer'; 192 layer.setAttribute('role', 'dialog'); 193 layer.setAttribute('aria-modal', 'true'); 194 195 const modal = document.createElement('div'); 196 modal.className = 'site-popup-modal'; 197 modal.dataset.popupId = String(item.id || ''); 198 199 const mobileBp = Number(item.mobile_breakpoint || 768); 200 if (window.innerWidth <= mobileBp) { 201 modal.classList.add('is-mobile'); 202 } else { 203 modal.classList.add('is-desktop'); 204 const top = Number(item.top_px || 100); 205 const left = Number(item.left_px || 100); 206 modal.style.top = `${Math.max(0, top)}px`; 207 modal.style.left = `${Math.max(0, left)}px`; 208 } 209 210 const body = document.createElement('div'); 211 body.className = 'site-popup-body'; 212 body.innerHTML = String(item.popup_html || '') 213 .replace(/\{\{\$POPUP_IMAGE\}\}/g, escapeHtml(item.image_url || '')) 214 .replace(/\{\{\$POPUP_LINK\}\}/g, escapeHtml(item.link_url || '#')) 215 .replace(/\{\{\$POPUP_TARGET\}\}/g, escapeHtml(item.link_target || '_self')) 216 .replace(/\{\{\$POPUP_TITLE\}\}/g, escapeHtml(item.title || 'popup')); 217 218 const footer = document.createElement('div'); 219 footer.className = 'site-popup-footer'; 220 221 const closeBtn = document.createElement('button'); 222 closeBtn.type = 'button'; 223 closeBtn.className = 'site-popup-close'; 224 closeBtn.textContent = 'ë«ê¸°'; 225 226 const hideBtn = document.createElement('button'); 227 hideBtn.type = 'button'; 228 hideBtn.className = 'site-popup-hide'; 229 const hideDays = Math.max(1, Number(item.hide_days) || 1); 230 hideBtn.textContent = hideDays === 1 ? 'í루 ëì ì보기' : `${hideDays}ì¼ê° ì보기`; 231 232 let keyHandler = null; 233 const dismiss = () => { 234 if (keyHandler) { 235 document.removeEventListener('keydown', keyHandler); 236 keyHandler = null; 237 } 238 try { 239 layer.remove(); 240 } catch {} 241 showNext(); 242 }; 243 244 closeBtn.addEventListener('click', dismiss); 245 hideBtn.addEventListener('click', () => { 246 setHidden(item, hideDays); 247 dismiss(); 248 }); 249 250 layer.addEventListener('click', (e) => { 251 if (e.target === layer) dismiss(); 252 }); 253 keyHandler = function escClose(e) { 254 if (e.key === 'Escape') dismiss(); 255 }; 256 document.addEventListener('keydown', keyHandler); 257 258 footer.appendChild(closeBtn); 259 footer.appendChild(hideBtn); 260 modal.appendChild(body); 261 modal.appendChild(footer); 262 layer.appendChild(modal); 263 return layer; 264 } 265 266 let queue = []; 267 let activeLayer = null; 268 let loading = false; 269 270 function showNext() { 271 if (activeLayer) { 272 try { 273 activeLayer.remove(); 274 } catch {} 275 activeLayer = null; 276 } 277 while (queue.length && isHidden(queue[0])) { 278 queue.shift(); 279 } 280 if (!queue.length) return; 281 const item = queue.shift(); 282 activeLayer = createLayer(item); 283 document.body.appendChild(activeLayer); 284 requestAnimationFrame(() => { 285 activeLayer?.classList.add('is-open'); 286 }); 287 } 288 289 async function loadPopups() { 290 if (loading) return; 291 loading = true; 292 try { 293 const path = normalizePath(location.pathname || '/'); 294 const siteApiBase = (window.__APP_CONFIG__ && window.__APP_CONFIG__.SITE_API_BASE) || '/api/site'; 295 const resp = await fetch(`${siteApiBase}/popup/active?path=${encodeURIComponent(path)}`, { 296 credentials: 'include', 297 }); 298 const json = await resp.json().catch(() => ({})); 299 if (!resp.ok || !json.success) return; 300 queue = Array.isArray(json.items) ? json.items.slice() : []; 301 if (!queue.length) return; 302 showNext(); 303 } catch {} 304 } 305 306 loadPopups(); 307});
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.