1/* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 2 ëìë리 ìº íì¥ â ê³µíµ ì¤í¬ë¦½í¸ 3 모ë íì´ì§ìì í¨ê» ìëë¤. 4 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 5 6/* ì¤í¬ë¡¤ ë±ì¥ ì ëë©ì´ì */ 7(function () { 8 const els = document.querySelectorAll('.reveal'); 9 if (!els.length) return; 10 if (!('IntersectionObserver' in window)) { els.forEach(e => e.classList.add('is-in')); return; } 11 const io = new IntersectionObserver((entries) => { 12 entries.forEach((entry) => { 13 if (entry.isIntersecting) { entry.target.classList.add('is-in'); io.unobserve(entry.target); } 14 }); 15 }, { threshold: 0.12, rootMargin: '0px 0px -8% 0px' }); 16 els.forEach((el) => io.observe(el)); 17})(); 18 19/* 모ë°ì¼ ë©ë´ */ 20(function () { 21 const menu = document.getElementById('mobileMenu'); 22 const open = document.getElementById('menuBtn'); 23 const close = document.getElementById('menuClose'); 24 if (!menu || !open) return; 25 const show = () => { menu.classList.remove('hidden'); document.body.style.overflow = 'hidden'; }; 26 const hide = () => { menu.classList.add('hidden'); document.body.style.overflow = ''; }; 27 open.addEventListener('click', show); 28 close && close.addEventListener('click', hide); 29 document.querySelectorAll('.mob-link').forEach(a => a.addEventListener('click', hide)); 30 document.addEventListener('keydown', (e) => { if (e.key === 'Escape') hide(); }); 31})(); 32 33/* íì´ì§ ì ë©ë´(ìë¸ë´ë¹) â ì§ê¸ ë³´ê³ ìë 구ìì íì 34 â» ìì ìë ì¬ê¸°ì scrollIntoView(behavior:'smooth') 를 ì¼ëë°, 35 ì¤í¬ë¡¤ ì¤ì ê³ì í¸ì¶ëë©´ì ì¬ì©ìì í ì¤í¬ë¡¤ê³¼ ë¶ëªí 36 íë©´ì´ ë²ë² ììµëë¤. ì§ê¸ì ë©ë´ ë ì ê°ë¡ ìì¹ë§ ì§ì ì®ê¸°ê³ 37 íì´ì§ ì¤í¬ë¡¤ìë ìëì§ ììµëë¤. */ 38(function () { 39 const bar = document.querySelector('.subnav'); 40 if (!bar) return; 41 const links = Array.from(bar.querySelectorAll('a[href^="#"]')); 42 if (!links.length) return; 43 44 const scroller = bar.querySelector('.no-bar') || bar; 45 46 const targets = links 47 .map(a => ({ a, el: document.getElementById(a.getAttribute('href').slice(1)) })) 48 .filter(x => x.el); 49 if (!targets.length) return; 50 51 let current = null; 52 53 // ì íë íëª©ì´ ë ë°ì¼ë¡ ìë ¸ì ëë§ ê°ë¡ë¡ ì´ì§ ì®ê¹ëë¤ 54 function keepVisible(link) { 55 if (scroller.scrollWidth <= scroller.clientWidth + 1) return; 56 const left = link.offsetLeft; 57 const right = left + link.offsetWidth; 58 const viewLeft = scroller.scrollLeft; 59 const viewRight = viewLeft + scroller.clientWidth; 60 if (left < viewLeft) scroller.scrollLeft = left - 12; 61 else if (right > viewRight) scroller.scrollLeft = right - scroller.clientWidth + 12; 62 } 63 64 function mark(link) { 65 if (link === current) return; // ë°ë ëë§ ìëëë¤ 66 current = link; 67 links.forEach(a => a.classList.toggle('is-current', a === link)); 68 keepVisible(link); 69 } 70 71 if (!('IntersectionObserver' in window)) return; 72 73 const seen = new Map(); 74 let queued = false; 75 76 function pick() { 77 queued = false; 78 let best = null, bestRatio = 0; 79 targets.forEach(({ a, el }) => { 80 const r = seen.get(el) || 0; 81 if (r > bestRatio) { bestRatio = r; best = a; } 82 }); 83 if (best) mark(best); 84 } 85 86 const io = new IntersectionObserver((entries) => { 87 entries.forEach(e => seen.set(e.target, e.isIntersecting ? e.intersectionRatio : 0)); 88 // ì¤í¬ë¡¤ ëì¤ ê³ì°ì´ ëª°ë¦¬ì§ ìëë¡ í íë ìì í ë²ë§ ì²ë¦¬í©ëë¤ 89 if (!queued) { queued = true; requestAnimationFrame(pick); } 90 }, { rootMargin: '-45% 0px -45% 0px', threshold: [0, 0.01, 0.5, 1] }); 91 92 targets.forEach(({ el }) => io.observe(el)); 93})(); 94 95/* ê°ì¤ í */ 96(function () { 97 const list = document.getElementById('stayTabs'); 98 if (!list) return; 99 const tabs = Array.from(list.querySelectorAll('[role="tab"]')); 100 101 function select(tab, focus) {
102 tabs.forEach((t) => { 103 const on = t === tab; 104 t.setAttribute('aria-selected', String(on)); 105 t.tabIndex = on ? 0 : -1; 106 const panel = document.getElementById(t.getAttribute('aria-controls')); 107 if (panel) panel.hidden = !on; 108 }); 109 if (focus) tab.focus(); 110 // í ë ë§ ê°ë¡ë¡ ì®ê¹ëë¤ (íì´ì§ ì¤í¬ë¡¤ì ê±´ëë¦¬ì§ ììµëë¤) 111 if (list.scrollWidth > list.clientWidth + 1) { 112 const l = tab.offsetLeft, rgt = l + tab.offsetWidth; 113 if (l < list.scrollLeft) list.scrollLeft = l - 12; 114 else if (rgt > list.scrollLeft + list.clientWidth) list.scrollLeft = rgt - list.clientWidth + 12; 115 } 116 } 117 118 tabs.forEach((tab) => tab.addEventListener('click', () => select(tab, false))); 119 list.addEventListener('keydown', (e) => { 120 const i = tabs.indexOf(document.activeElement); 121 if (i < 0) return; 122 let n = null; 123 if (e.key === 'ArrowRight') n = (i + 1) % tabs.length; 124 if (e.key === 'ArrowLeft') n = (i - 1 + tabs.length) % tabs.length; 125 if (e.key === 'Home') n = 0; 126 if (e.key === 'End') n = tabs.length - 1; 127 if (n === null) return; 128 e.preventDefault(); 129 select(tabs[n], true); 130 }); 131})(); 132 133 134/* ê°ì¤ ì¬ì§ ì¬ë¼ì´ë 135 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 136 â ì¬ì§ì ë£ê³ ë¹¼ë ¤ë©´ HTML ì .rslide ììì <img> 를 â 137 â ì¶ê°íê±°ë ì§ì°ê¸°ë§ íë©´ ë©ëë¤. íì´í·ì ·ì¥ì íìë â 138 â ì¬ì§ ìì ë§ì¶° ìëì¼ë¡ ë§ë¤ì´ì§ëë¤. â 139 ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 140(function () { 141 document.querySelectorAll('.rslide').forEach(function (box) { 142 const imgs = Array.from(box.querySelectorAll(':scope > img')); 143 if (!imgs.length) return; 144 145 const track = document.createElement('div'); 146 track.className = 'rslide-track'; 147 imgs.forEach(im => track.appendChild(im)); 148 box.appendChild(track); 149 150 let i = 0; 151 const many = imgs.length > 1; 152 153 const count = document.createElement('p'); 154 count.className = 'rslide-count'; 155 box.appendChild(count); 156 157 let dots = []; 158 if (many) { 159 const prev = document.createElement('button'); 160 prev.type = 'button'; prev.className = 'rslide-btn prev'; 161 prev.setAttribute('aria-label', 'ì´ì ì¬ì§'); 162 prev.innerHTML = '<iconify-icon icon="solar:alt-arrow-left-linear" class="text-xl"></iconify-icon>'; 163 const next = document.createElement('button'); 164 next.type = 'button'; next.className = 'rslide-btn next'; 165 next.setAttribute('aria-label', 'ë¤ì ì¬ì§'); 166 next.innerHTML = '<iconify-icon icon="solar:alt-arrow-right-linear" class="text-xl"></iconify-icon>'; 167 box.append(prev, next); 168 prev.addEventListener('click', () => go(i - 1)); 169 next.addEventListener('click', () => go(i + 1)); 170 171 const dotWrap = document.createElement('div'); 172 dotWrap.className = 'rslide-dots'; 173 dots = imgs.map((_, n) => { 174 const d = document.createElement('button'); 175 d.type = 'button'; 176 d.setAttribute('aria-label', (n + 1) + 'ë²ì§¸ ì¬ì§ 보기'); 177 d.addEventListener('click', () => go(n)); 178 dotWrap.appendChild(d); 179 return d; 180 }); 181 box.appendChild(dotWrap); 182 183 // ì¢ì° ë기기 (ìê°ë½ · í¤ë³´ë) 184 let x0 = null; 185 box.addEventListener('touchstart', (e) => { x0 = e.touches[0].clientX; }, { passive: true }); 186 box.addEventListener('touchend', (e) => { 187 if (x0 === null) return; 188 const dx = e.changedTouches[0].clientX - x0; 189 if (Math.abs(dx) > 40) go(dx < 0 ? i + 1 : i - 1); 190 x0 = null; 191 }); 192 box.tabIndex = 0; 193 box.addEventListener('keydown', (e) => { 194 if (e.key === 'ArrowLeft') { e.preventDefault(); go(i - 1); } 195 if (e.key === 'ArrowRight') { e.preventDefault(); go(i + 1); } 196 }); 197 } 198 199 function go(n) { 200 i = (n + imgs.length) % imgs.length; 201 track.style.transform = 'translateX(-' + (i * 100) + '%)';
202 dots.forEach((d, k) => d.setAttribute('aria-current', String(k === i))); 203 count.textContent = (i + 1) + ' / ' + imgs.length; 204 } 205 go(0); 206 }); 207})(); 208 209/* ââââââââââââââââââââââââââââââââââââââââââââââââââââââ 210 ë°°ì¹ë í¬ê² 보기 211 ë°°ì¹ë를 ë르면 ì ì²´ íë©´ì¼ë¡ ì´ë¦¬ê³ , 212 ë¨ê³ë³ë¡ íëíê±°ë ëì´ì ì´ëí ì ììµëë¤. 213ââââââââââââââââââââââââââââââââââââââââââââââââââââââ */ 214(function () { 215 const dlg = document.getElementById('mapDlg'); 216 const open = document.getElementById('mapOpen'); 217 if (!dlg || !open || !dlg.showModal) return; 218 219 const scroll = document.getElementById('mapScroll'); 220 const img = document.getElementById('mapZoomImg'); 221 const pct = document.getElementById('mapPct'); 222 223 const STEPS = [1, 1.6, 2.4, 3.5, 5]; // ë°°ì¨ ë¨ê³ 224 let zi = 0; 225 let dragged = false; 226 227 // íë©´ ëë¹ì ë± ë§ë í¬ê¸° (ì¢ì° ì¬ë°± p-3 = 12px ì©) 228 function fitWidth() { 229 return Math.max(80, scroll.clientWidth - 24); 230 } 231 232 function setZoom(next, focus) { 233 const oldW = img.offsetWidth || fitWidth(); 234 zi = Math.max(0, Math.min(STEPS.length - 1, next)); 235 236 const newW = fitWidth() * STEPS[zi]; 237 const ratio = newW / oldW; 238 const fx = focus ? focus.x : scroll.clientWidth / 2; 239 const fy = focus ? focus.y : scroll.clientHeight / 2; 240 241 img.style.width = newW + 'px'; 242 pct.textContent = Math.round(STEPS[zi] * 100) + '%';
243 scroll.style.cursor = zi >= STEPS.length - 1 ? 'zoom-out' : 'zoom-in'; 244 245 // ë³´ê³ ìë ì§ì ì´ ê·¸ëë¡ ë¨ëë¡ ì¤í¬ë¡¤ ìì¹ë¥¼ ì®ê¹ëë¤ 246 scroll.scrollLeft = (scroll.scrollLeft + fx) * ratio - fx; 247 scroll.scrollTop = (scroll.scrollTop + fy) * ratio - fy; 248 } 249 250 function reset() { 251 zi = 0; 252 img.style.width = fitWidth() + 'px'; 253 pct.textContent = '100%'; 254 scroll.style.cursor = 'zoom-in'; 255 scroll.scrollLeft = 0; 256 scroll.scrollTop = 0; 257 } 258 259 open.addEventListener('click', () => { 260 dlg.showModal(); 261 reset(); // ì°½ì´ ì´ë¦° ë¤ë¼ì¼ ì¤ì ëë¹ë¥¼ ì ì ììµëë¤ 262 }); 263 264 dlg.addEventListener('click', (e) => { 265 const btn = e.target.closest('[data-map]'); 266 if (!btn) return; 267 const act = btn.dataset.map; 268 if (act === 'close') dlg.close(); 269 if (act === 'in') setZoom(zi + 1); 270 if (act === 'out') setZoom(zi - 1); 271 }); 272 273 // ë°°ì¹ë를 ë르면 ê·¸ ì§ì ì ì¤ì¬ì¼ë¡ í ë¨ê³ì© íë 274 img.addEventListener('click', (e) => { 275 if (dragged) { dragged = false; return; } 276 const r = scroll.getBoundingClientRect(); 277 setZoom(zi >= STEPS.length - 1 ? 0 : zi + 1, { x: e.clientX - r.left, y: e.clientY - r.top }); 278 }); 279 280 // ë§ì°ì¤ë¡ ëì´ì ì´ë 281 let drag = null; 282 scroll.addEventListener('pointerdown', (e) => { 283 if (e.pointerType === 'touch') return; // ìê°ë½ì 기본 ì¤í¬ë¡¤ ì¬ì© 284 drag = { x: e.clientX, y: e.clientY, l: scroll.scrollLeft, t: scroll.scrollTop }; 285 dragged = false; 286 try { scroll.setPointerCapture(e.pointerId); } catch (err) {} 287 }); 288 scroll.addEventListener('pointermove', (e) => { 289 if (!drag) return; 290 const dx = e.clientX - drag.x, dy = e.clientY - drag.y; 291 if (Math.abs(dx) > 3 || Math.abs(dy) > 3) dragged = true;
292 scroll.scrollLeft = drag.l - dx; 293 scroll.scrollTop = drag.t - dy; 294 }); 295 ['pointerup', 'pointercancel', 'pointerleave'].forEach((ev) => 296 scroll.addEventListener(ev, () => { drag = null; }) 297 ); 298 299 // Ctrl + í ë¡ íë·ì¶ì 300 scroll.addEventListener('wheel', (e) => { 301 if (!e.ctrlKey) return; 302 e.preventDefault(); 303 const r = scroll.getBoundingClientRect(); 304 setZoom(zi + (e.deltaY < 0 ? 1 : -1), { x: e.clientX - r.left, y: e.clientY - r.top }); 305 }, { passive: false }); 306 307 // í¤ë³´ë 308 dlg.addEventListener('keydown', (e) => { 309 if (e.key === '+' || e.key === '=') { e.preventDefault(); setZoom(zi + 1); } 310 if (e.key === '-' || e.key === '_') { e.preventDefault(); setZoom(zi - 1); } 311 if (e.key === '0') { e.preventDefault(); reset(); } 312 }); 313 314 // íë©´ í¬ê¸°ê° ë°ëë©´ ë¤ì ë§ì¶¥ëë¤ 315 let rt; 316 window.addEventListener('resize', () => { 317 if (!dlg.open) return; 318 clearTimeout(rt); 319 rt = setTimeout(() => setZoom(zi), 150); 320 }); 321})(); 322 323/* í¸í° ì°ë */ 324(function () { 325 const y = document.getElementById('year'); 326 if (y) y.textContent = new Date().getFullYear(); 327})();
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.