PageSourceSearch

https://www.kitech.re.kr/js/layout.js?20260729144837

js kitech.re.kr collected 2026-10-01 11:54:51 UTC 23,486 bytes, 778 lines download raw bytes

1function layoutEvent() {
2	const container = document.querySelector('#container');
3	const mainContent = container?.querySelector('.main-content');
4	const header = document.querySelector('#header');
5
6	if (container && mainContent && header) {
7		header.style.position = 'fixed';
8	}
9	
10	function updateHeaderState() {
11		const top = window.scrollY;
12		if (top > 0) {
13			header.classList.add('active');
14		} else {
15			header.classList.remove('active');
16		}
17	}
18	updateHeaderState();
19
20	window.addEventListener('scroll', updateHeaderState);
21
22	const cursor = document.getElementById("cursor-custom");
23	if (!cursor) return;
24	const  radius = cursor.offsetHeight / 2;
25
26	document.addEventListener("mousemove", (e) => {
27		let top = e.clientY - radius,
28		left = e.clientX - radius;
29
30		cursor.style = `top: ${top}px; left: ${left}px`;
31
32		const target = e.target;
33		if (target.closest("button, a")) {
34			cursor.classList.add("hover");
35		} else {
36			cursor.classList.remove("hover");
37		}
38	});
39	
40	let handleStorage = {
41		// 스토리지에 데이터 쓰기(이름, 만료일)
42		setStorage: function (name, exp) {
43			// 만료 시간 구하기(exp를 ms단위로 변경)
44			let date = new Date();
45			date = date.setTime(date.getTime() + exp * 24 * 60 * 60 * 1000);
46
47			// 로컬 스토리지에 저장하기
48			// (값을 따로 저장하지 않고 만료 시간을 저장)
49			localStorage.setItem(name, date)
50		},
51		// 스토리지 읽어오기
52		getStorage: function (name) {
53			let now = new Date();
54			now = now.setTime(now.getTime());
55			// 현재 시각과 스토리지에 저장된 시각을 각각 비교하여
56			// 시간이 남아 있으면 true, 아니면 false 리턴
57			return parseInt(localStorage.getItem(name)) > now
58		}
59	};
60
61	function escapeAttribute(value) {
62		return String(value ?? '')
63			.replace(/&/g, '&')
64			.replace(/"/g, '"')
65			.replace(/</g, '&lt;')
66			.replace(/>/g, '&gt;');
67	}
68
69	function closeAllMenus() {
70		document.querySelectorAll('.gnb-wrap .dep1.focus').forEach((dep1) => {
71			const subGnbWrap = dep1.querySelector('.sub-gnb-wrap');
72			const depth2List = dep1.querySelector('.depth2-list');
73
74			if (subGnbWrap) subGnbWrap.style.height = '0';
75			if (depth2List) depth2List.classList.remove('active');
76			dep1.classList.remove('focus');
77		});
78	}
79
80	function toggleMenu(element) {
81		const dep1 = element?.closest('.dep1');
82		if (!dep1) return;
83
84		if (dep1.classList.contains('focus')) return;
85
86		closeAllMenus();
87
88		const subGnbWrap = dep1.querySelector('.sub-gnb-wrap');
89		const depth2List = dep1.querySelector('.depth2-list');
90		if (!subGnbWrap || !depth2List) return;
91
92		dep1.classList.add('focus');
93		subGnbWrap.style.height = depth2List.offsetHeight + 'px';
94		depth2List.classList.add('active');
95	}
96
97	// GNB blur, focus, mouseover 이벤트
98	document.querySelectorAll('.gnb-wrap a').forEach(anchor => {
99		anchor.addEventListener('focus', () => toggleMenu(anchor));
100	});
101
102	document.querySelectorAll('.gnb-wrap > ul > li.dep1').forEach(listItem => {
103		listItem.addEventListener('mouseenter', () => toggleMenu(listItem));
104	});
105
106	const gnbWrap = document.querySelector('.gnb-wrap');
107	if (gnbWrap) {
108		gnbWrap.addEventListener('mouseleave', closeAllMenus);
109		gnbWrap.addEventListener('focusout', (e) => {
110			const nextFocus = e.relatedTarget;
111
112			if (nextFocus && gnbWrap.contains(nextFocus)) return;
113
114			closeAllMenus();
115		});
116	}
117
118	function initPcHeaderDropdown() {
119		const dropdowns = document.querySelectorAll('.header-dropdown');
120		if (!dropdowns.length) return;
121
122		// 드롭다운 요소를 찾기 위한 Map 생성 (선택 사항이지만 성능에 도움)
123		const dropdownMap = new Map();
124		dropdowns.forEach(dropdown => {
125			const btn = dropdown.querySelector('.header-dropdown-default');
126			const menu = dropdown.querySelector('.header-dropdown-menu');
127			const inner = dropdown.querySelector('.header-dropdown-menu-inner');
128
129			if (!btn || !menu || !inner) return;
130
131			// 초기 상태 설정
132			btn.setAttribute('aria-expanded', 'false');
133			menu.setAttribute('aria-hidden', 'true');
134			menu.style.height = '0';
135			menu.style.display = 'none';
136			menu.style.overflow = 'hidden';
137
138			dropdownMap.set(dropdown, { btn, menu, inner });
139
140			btn.addEventListener('click', (e) => {
141				e.stopPropagation(); // 버튼 클릭 시 이벤트 버블링 방지
142
143				const isOpen = btn.classList.contains('open');
144
145				// 다른 드롭다운 닫기
146				dropdowns.forEach(other => {
147					if (other !== dropdown) {
148					closeDropdown(other);
149					}
150				});
151
152				// 현재 드롭다운 열기/닫기
153				isOpen ? closeDropdown(dropdown) : openDropdown(dropdown);
154			});
155
156			dropdown.addEventListener('keydown', (e) => {
157				if (e.key === 'Escape' && btn.classList.contains('open')) {
158					e.preventDefault();
159					closeDropdown(dropdown);
160					btn.focus();
161				}
162			});
163		});
164
165		// 외부 클릭 시 전체 닫기 로직 수정: 드롭다운 내부 클릭 시에는 닫지 않음
166		document.addEventListener('click', (e) => {
167			dropdowns.forEach(dropdown => {
168			// 클릭된 요소(e.target)의 부모 중 가장 가까운 .header-dropdown 요소를 찾습니다.
169			// 만약 찾은 요소가 현재 드롭다운 요소가 아니라면, 외부 클릭으로 간주하고 닫습니다.
170				if (!dropdown.contains(e.target)) {
171					closeDropdown(dropdown);
172				}
173			});
174		});
175	}
176
177	/* ===== 열기 ===== */
178	function openDropdown(dropdown) {
179		const btn = dropdown.querySelector('.header-dropdown-default');
180		const menu = dropdown.querySelector('.header-dropdown-menu');
181		const inner = dropdown.querySelector('.header-dropdown-menu-inner');
182		if (!btn || !menu || !inner) return;
183
184		btn.classList.add('open');
185		btn.setAttribute('aria-expanded', 'true');
186
187		menu.setAttribute('aria-hidden', 'false');
188		menu.style.display = 'block';
189
190		// SCSS 수정에 따라 inner의 높이가 아닌 menu의 높이로 계산
191		// (menu가 absolute이고 inner를 감싸고 있다면, inner의 scrollHeight를 사용)
192		const targetHeight = inner.scrollHeight; 
193
194		menu.style.height = '0px';
195		menu.style.transition = 'height 0.3s ease';
196
197		requestAnimationFrame(() => {
198			menu.style.height = `${targetHeight}px`;
199		});
200
201		menu.addEventListener(
202			'transitionend',
203			() => {
204				menu.style.height = 'auto';
205				menu.style.transition = '';
206			},
207			{ once: true }
208		);
209	}
210
211	/* ===== 닫기 ===== */
212	function closeDropdown(dropdown) {
213		const btn = dropdown.querySelector('.header-dropdown-default');
214		const menu = dropdown.querySelector('.header-dropdown-menu');
215		
216		// 방어 로직 강화: 'open' 클래스가 없거나 이미 닫혀 있으면 종료
217		if (!btn || !menu || !btn.classList.contains('open') || menu.style.display === 'none') return;
218
219		btn.classList.remove('open');
220		btn.setAttribute('aria-expanded', 'false');
221		menu.setAttribute('aria-hidden', 'true');
222
223		const currentHeight = menu.offsetHeight;
224		menu.style.height = `${currentHeight}px`;
225		menu.style.transition = 'height 0.3s ease';
226
227		requestAnimationFrame(() => {
228			menu.style.height = '0px';
229		});
230
231		menu.addEventListener(
232			'transitionend',
233			() => {
234				menu.style.display = 'none';
235				menu.style.transition = '';
236			},
237			{ once: true }
238		);
239	}
240
241	initPcHeaderDropdown();
242
243	// 탑배너
244	const  topBannerSwiper = new Swiper(".top-banner .swiper", {
245		direction: "vertical",
246		slidesPerView: 1,
247		speed: 800,
248		autoplay: {
249			delay: 3000,
250			disableOnInteraction: false,
251		},
252		loop: true,
253		navigation: {
254			nextEl: '.top-banner .next',
255			prevEl: '.top-banner .prev',
256		},
257		on: {
258			init: function () {
259				const swiper = this;
260				const topBanner = swiper.el.closest(".top-banner");
261				const toggleBtn = topBanner?.querySelector(".swiper-control .pause");
262
263				if (!toggleBtn || !swiper.autoplay) return;
264
265				toggleBtn.textContent = "정지";
266				toggleBtn.classList.remove("play");
267
268				toggleBtn.addEventListener("click", () => {
269					if (swiper.autoplay.running) {
270						swiper.autoplay.stop();
271						toggleBtn.classList.add("play");
272						toggleBtn.textContent = "재생";
273					} else {
274						swiper.autoplay.start();
275						toggleBtn.classList.remove("play");
276						toggleBtn.textContent = "정지";
277					}
278				});
279			},
280		},
281	});
282
283	// 통합검색창
284	const searchBtn = document.querySelector('.hd-utils .btn-search');
285	const headSearch = document.querySelector('.hd-search-wrap');
286	const searchCloseBtn = headSearch?.querySelector('.search-close');
287	const headerSearchInput = headSearch?.querySelector('#headerSearchText');
288	const btnMenu = document.querySelector('#header > .hd-wrap > .hd-utils > .link.btn-menu');
289	let headerSearchFocusTimer;
290
291	function focusHeaderSearchInput() {
292		if (!headerSearchInput || !headSearch?.classList.contains('on')) return;
293
294		if (document.activeElement !== headerSearchInput) {
295			headerSearchInput.focus({preventScroll: true});
296		}
297	}
298
299	function setHeaderSearchState(isOpen, returnFocus = false) {
300		if (!searchBtn || !headSearch) return;
301
302		headSearch.classList.toggle('on', isOpen);
303		searchBtn.setAttribute('aria-expanded', String(isOpen));
304		headSearch.setAttribute('aria-hidden', String(!isOpen));
305		window.clearTimeout(headerSearchFocusTimer);
306
307		if (isOpen) {
308			focusHeaderSearchInput();
309			requestAnimationFrame(focusHeaderSearchInput);
310			headerSearchFocusTimer = window.setTimeout(focusHeaderSearchInput, 350);
311		} else if (returnFocus) {
312			searchBtn.focus();
313		}
314	}
315
316	if (searchBtn && headSearch && searchCloseBtn) {
317		searchBtn.addEventListener('click', function () {
318			setHeaderSearchState(!headSearch.classList.contains('on'));
319		});
320
321		searchCloseBtn.addEventListener('click', function () {
322			setHeaderSearchState(false, true);
323		});
324
325		headSearch.addEventListener('focusout', function (e) {
326			const nextFocus = e.relatedTarget;
327			const isMovingInsidePanel = nextFocus && headSearch.contains(nextFocus);
328
329			if (isMovingInsidePanel || nextFocus === searchBtn) return;
330
331			setHeaderSearchState(false);
332			btnMenu?.focus({preventScroll: true});
333		});
334
335		document.addEventListener('keydown', function (e) {
336			if (e.key === 'Escape' && headSearch.classList.contains('on')) {
337				e.preventDefault();
338				setHeaderSearchState(false, true);
339			}
340		});
341	}
342
343	// aside
344	// 사이트맵
345	const aside = document.getElementById('aside');
346	const asideBg = document.querySelector('.aside-bg');
347
348	btnMenu.addEventListener('click', function (e) {
349		// 모바일일 때만 제어
350		if (window.innerWidth <= 1023) {
351			e.preventDefault();
352
353			const isOpen = aside.classList.contains('open');
354
355			if (isOpen) {
356				closeAside();
357			} else {
358				openAside();
359			}
360		}
361	});
362
363	// 배경 클릭 시 닫기
364	asideBg?.addEventListener('click', closeAside);
365
366	function openAside() {
367		aside.removeAttribute('inert');
368		aside.setAttribute('aria-hidden', 'false');
369		aside.classList.add('open');
370		asideBg.classList.add('show');
371		btnMenu.classList.add('on');
372		document.body.style.overflow = 'hidden';
373
374		btnMenu.setAttribute('aria-expanded', 'true');
375	}
376
377	function closeAside() {
378		if (aside.contains(document.activeElement)) {
379			btnMenu.focus({preventScroll: true});
380		}
381
382		aside.classList.remove('open');
383		asideBg?.classList.remove('show');
384		btnMenu.classList.remove('on');
385		document.body.style.overflow = '';
386
387		btnMenu.setAttribute('aria-expanded', 'false');
388		aside.setAttribute('aria-hidden', 'true');
389		aside.setAttribute('inert', '');
390	}
391	
392	// 리사이즈 시 PC로 전환되면 상태 초기화
393	window.addEventListener('resize', () => {
394		if (window.innerWidth > 1023) {
395			closeAside();
396		}
397	});
398
399	function toggleDepth3Visibility(li) {
400		const dep3Ul = li.querySelector('.dep3-ul');
401		if (!dep3Ul) return;
402
403		if (li.classList.contains('on')) {
404			dep3Ul.style.height = '0px';
405			dep3Ul.style.overflow = 'hidden';
406			dep3Ul.style.visibility = 'hidden';
407			li.classList.remove('on');
408		} else {
409			dep3Ul.style.height = `${dep3Ul.scrollHeight}px`;
410			dep3Ul.style.overflow = 'visible';
411			dep3Ul.style.visibility = 'visible';
412			li.classList.add('on');
413		}
414	}
415	function closeAllDep3(dep1El) {
416		dep1El.querySelectorAll('.dep2-ul > li.on').forEach(li => {
417			const dep3Ul = li.querySelector('.dep3-ul');
418			if (!dep3Ul) return;
419
420			dep3Ul.style.height = '0px';
421			dep3Ul.style.overflow = 'hidden';
422			dep3Ul.style.visibility = 'hidden';
423			li.classList.remove('on');
424		});
425	}
426	function moDepth2(e) {
427		e.preventDefault();
428		const depth2 = e.currentTarget;
429		const parentLi = depth2.parentElement;
430
431		if (!parentLi.querySelector('.dep3-ul')) return;
432
433		const siblings = parentLi.parentElement.children;
434		Array.from(siblings).forEach(sibling => {
435			if (sibling !== parentLi) {
436				const siblingDep3Ul = sibling.querySelector('.dep3-ul');
437				if (siblingDep3Ul) {
438					siblingDep3Ul.style.height = '0px';
439					siblingDep3Ul.style.overflow = 'hidden';
440					siblingDep3Ul.style.visibility = 'hidden';
441					sibling.classList.remove('on');
442				}
443			}
444		});
445
446		toggleDepth3Visibility(parentLi);
447	}
448
449	document.querySelectorAll('#asideWrap .dep1').forEach((dep1) => {
450		const dep2Wrap = dep1.querySelector('.dep2-wrap');
451		if (dep2Wrap) {
452			dep1.querySelector('.dep1-tit').addEventListener('click', (e) => {
453				e.preventDefault();
454				if (dep1.classList.contains('on')) return;
455
456				document.querySelectorAll('#asideWrap .dep1').forEach((otherDep1) => {
457					if (otherDep1 !== dep1) {
458						otherDep1.classList.remove('on');
459						otherDep1.querySelector('.dep2-wrap')?.classList.remove('on');
460						
461						// closeAllDep3(otherDep1);
462					}
463				});
464
465				dep1.classList.add('on');
466				dep2Wrap.classList.toggle('on');
467			});
468		}
469	});
470
471	document.querySelectorAll('#asideWrap .dep2-ul > li').forEach((depth) => {
472		const dep3Ul = depth.querySelector('.dep3-ul');
473		if (dep3Ul) {
474			depth.querySelector('a').addEventListener('click', moDepth2);
475		}
476	});
477
478	const firstDepth1 = document.querySelector('#asideWrap > ul').firstElementChild;
479	firstDepth1?.classList.add('on');
480	firstDepth1?.querySelector('.dep2-wrap').classList.add('on');
481
482	document.querySelectorAll('#asideWrap .dep2-ul > li').forEach(li => {
483		if (li.querySelector('.dep3-ul')) {
484			li.classList.add('on');
485		}
486	});
487
488	// 퀵메뉴
489	const quickBtn = document.querySelector('.btn-quick');
490	const quickMenuWrap = document.querySelector('#quick-group');
491    const quickWrapper = document.querySelector('#quick-group .swiper-wrapper');
492	const quickCloseBtn = document.querySelector('#quick-group .btn-close');
493
494	function setQuickMenuOpen(isOpen, shouldReturnFocus = true) {
495		if (!quickMenuWrap || !quickBtn) return;
496
497		if (isOpen) {
498			quickMenuWrap.hidden = false;
499			quickMenuWrap.removeAttribute('inert');
500			quickMenuWrap.setAttribute('aria-hidden', 'false');
501			quickMenuWrap.classList.add('on');
502			quickBtn.setAttribute('aria-pressed', 'true');
503			return;
504		}
505
506		quickMenuWrap.classList.remove('on');
507
508		if (shouldReturnFocus && quickMenuWrap.contains(document.activeElement)) {
509			quickBtn.focus({preventScroll: true});
510		}
511
512		quickMenuWrap.setAttribute('aria-hidden', 'true');
513		quickMenuWrap.setAttribute('inert', '');
514		quickMenuWrap.hidden = true;
515		quickBtn.setAttribute('aria-pressed', 'false');
516	}
517
518	quickMenuWrap?.addEventListener('focusout', function (event) {
519		if (!quickMenuWrap.classList.contains('on')) return;
520
521		const nextFocusedElement = event.relatedTarget;
522		if (nextFocusedElement && quickMenuWrap.contains(nextFocusedElement)) return;
523
524		setQuickMenuOpen(false, false);
525	});
526
527	if (quickCloseBtn) {
528		quickCloseBtn.addEventListener('click', function (e) {
529			e.preventDefault();
530			e.stopPropagation();
531			setQuickMenuOpen(false);
532		});
533	}
534	quickBtn?.addEventListener('click', function () {
535		const shouldOpen = !quickMenuWrap.classList.contains('on');
536		setQuickMenuOpen(shouldOpen);
537
538            if (!shouldOpen || quickWrapper.dataset.loaded === 'true') return;
539
540            // 퀵메뉴 포토뉴스
541            fetch('/api/v1/gallery/list', {
542                method: 'GET',
543                headers: {
544                    'Content-Type': 'application/json'
545                }
546            })
547                .then(handleFetchResponse)
548                .then(result => {
549                    const dataList = result.data || result;
550
551                    let html = '';
552                    dataList.forEach(item => {
553                        const dateStr = item.createdAt ? item.createdAt.substring(0, 10) : '';
554                        const image = (item.images && item.images.length > 0) ? item.images[0] : null;
555                        const imgUuid = image ? image.uuid : '';
556                        const imgSrc = imgUuid ? `/api/v1/attachment/${imgUuid}?size=500` : '/images/common/logo.png';
557                        const imgAlt = escapeAttribute(image && image.description ? image.description : item.title);
558                        const linkUrl = item.linkUrl;
559
560                        html += `
561                                <div class="swiper-slide">
562                                    <a href="${linkUrl}" class="photo-box">
563                                        <div class="imgbox">
564                                            <img src="${imgSrc}" alt="${imgAlt}">
565                                        </div>
566                                        <div class="text-wrap">
567                                            <p>${item.title}</p>
568                                            <p class="date">${dateStr}</p>
569                                        </div>
570                                    </a>
571                                </div>
572                            `;
573                    });
574
575                    quickWrapper.innerHTML = html;
576                    quickPhotoSwiper.update();
577                    quickPhotoSwiper.slideTo(0);
578
579                    if(quickPhotoSwiper.autoplay && !quickPhotoSwiper.autoplay.running) {
580                        quickPhotoSwiper.autoplay.start();
581                    }
582
583                    quickWrapper.dataset.loaded = 'true';
584            })
585                .catch(error => {
586                    console.log("API Error ", error);
587                })
588	});
589
590    function initQuickSwiper() {
591        window.quickMenuSwiper = new Swiper('.quick-menu-wrap .swiper', {
592            slidesPerView: 1, // 혹은 'auto'
593            spaceBetween: 10,
594            // 기타 옵션...
595        });
596    }
597
598	const quickPhotoSwiper = new Swiper(".quick-menu-wrap .swiper", {
599		speed: 800,
600		autoplay: {
601			delay: 5000,
602			disableOnInteraction: false,
603		},
604		navigation: {
605			nextEl: '.quick-menu-wrap .next',
606			prevEl: '.quick-menu-wrap .prev',
607		},
608		on: {
609			init: function () {
610				const swiper = this;
611				const toggleBtn = swiper.el.querySelector(".pause");
612
613				if (!toggleBtn || !swiper.autoplay) return;
614
615				toggleBtn.textContent = "정지";
616				toggleBtn.classList.remove("play");
617
618				toggleBtn.addEventListener("click", () => {
619					if (swiper.autoplay.running) {
620						swiper.autoplay.stop();
621						toggleBtn.classList.add("play");
622						toggleBtn.textContent = "재생";
623					} else {
624						swiper.autoplay.start();
625						toggleBtn.classList.remove("play");
626						toggleBtn.textContent = "정지";
627					}
628				});
629			},
630		},
631	});
632
633	// 탑버튼
634	const goTopButton = document.querySelector('.go-top');
635
636	if (goTopButton) {
637		window.addEventListener('scroll', () => {
638			if (window.scrollY > window.innerHeight * 0.5) {
639				goTopButton.classList.add('active');
640			} else {
641				goTopButton.classList.remove('active');
642			}
643		});
644
645		goTopButton.addEventListener('click', () => {
646			window.scrollTo({
647				top: 0,
648				behavior: 'smooth'
649			});
650		});
651	}
652
653	// aside 바로가기
654    const asideLinkContainer = document.querySelector('.aside-link');
655    if (asideLinkContainer) {
656        const openBtn = asideLinkContainer.querySelector('.btn-asidelink');
657        const asideGroup = asideLinkContainer.querySelector('#aside-group');
658        const closeBtn = asideLinkContainer.querySelector('.btn-close');
659
660        if (openBtn && asideGroup) {
661            openBtn.addEventListener('click', (e) => {
662                e.preventDefault();
663                asideGroup.classList.add('on');
664                openBtn.setAttribute('aria-pressed', 'true');
665                console.log('메뉴 열림');
666            });
667        }
668
669        if (closeBtn && asideGroup) {
670            closeBtn.addEventListener('click', (e) => {
671                e.preventDefault();
672                asideGroup.classList.remove('on');
673                openBtn.setAttribute('aria-pressed', 'false');
674            });
675        }
676    }
677}
678
679
680let loadSocket = {
681	'header': false,
682	'aside': false,
683	'footer': false
684}
685
686window.addEventListener('DOMContentLoaded', () => {
687	const includeTag = document.querySelectorAll('.js-include');
688	if (includeTag.length === 0) {
689		layoutEvent();
690	} else {
691		includeTag.forEach((divBox, idx) => {
692			fetch(divBox.dataset.include)
693				.then(res => res.text())
694				.then(function (data) {
695					divBox.innerHTML = data;
696					loadSocket[divBox.dataset.event] = true;
697
698					if (loadSocket.header && loadSocket.aside && loadSocket.footer) {
699						layoutEvent();
700					}
701				});
702		});
703	}
704});
705
706document.addEventListener('DOMContentLoaded', function () {
707  	// 1) 바깥 클릭하면 전체 셀렉트 박스 닫기
708	document.addEventListener('click', function (e) {
709		const isInsideSelectBox = e.target.closest('.select-box');
710
711		if (isInsideSelectBox) return; // select-box 안 클릭이면 무시
712
713		document.querySelectorAll('.select-box .select').forEach(function (selectEl) {
714			selectEl.classList.remove('on');
715			const optionList = selectEl.nextElementSibling;
716			if (optionList) {
717				optionList.classList.remove('open');
718			}
719		});
720	});
721
722  	// 2) .select-box .select 클릭 시 열기/닫기 (이벤트 위임)
723	document.addEventListener('click', function (event) {
724		const selectBtn = event.target.closest('.select-box .select');
725		if (!selectBtn) return; // select 버튼이 아니면 무시
726
727		// 자신 제외 다른 select는 모두 닫기
728		document.querySelectorAll('.select-box .select').forEach(function (btn) {
729			if (btn !== selectBtn) {
730				btn.classList.remove('on');
731				const otherList = btn.nextElementSibling;
732				if (otherList && otherList.classList.contains('option-list')) {
733				otherList.classList.remove('open');
734				}
735			}
736		});
737
738		// 현재 버튼 토글
739		selectBtn.classList.toggle('on');
740
741		const optionList = selectBtn.nextElementSibling;
742		if (optionList && optionList.classList.contains('option-list')) {
743			if (selectBtn.classList.contains('on')) {
744				optionList.classList.add('open');   // slideDown
745			} else {
746				optionList.classList.remove('open'); // slideUp
747			}
748		}
749	});
750
751  	// 3) radio 선택 시 텍스트 변경 + 닫기 (이벤트 위임)
752	document.addEventListener('change', function (e) {
753		const radio = e.target;
754		const li = radio.closest('li');
755		if (!radio.matches('.select-box .option-list > li input[type="radio"]')) return;
756		if (!radio.checked) return;
757		if (li && li.classList.contains('soldout') || li.classList.contains('disabled')) return;
758
759		const label = radio.nextElementSibling;
760		const span = label ? label.querySelector('span') : null;
761		const labelText = span ? span.textContent : '';
762
763		const optionList = radio.closest('.option-list');
764		const selectBox = radio.closest('.select-box');
765		if (!optionList || !selectBox) return;
766
767		const selectBtn = selectBox.querySelector('.select');
768		if (!selectBtn) return;
769
770		// 버튼 텍스트 변경
771		selectBtn.textContent = labelText;
772
773		// 상태/스타일 변경
774		selectBox.classList.add('selected');
775		selectBtn.classList.remove('on');
776		optionList.classList.remove('open'); // 닫기 애니메이션
777	});
778});

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.