PageSourceSearch

https://hangeul.go.kr/js/ui-script.js

js hangeul.go.kr collected 2026-10-02 06:04:54 UTC 96,489 bytes, 2,672 lines download raw bytes

1"use strict";
2
3// 윈도우 사이즈 체크
4const windowSize = {
5  winSize: null,
6  breakPoint: 1024,
7
8  setWinSize() {
9    this.winSize = window.innerWidth >= this.breakPoint ? "pc" : "mob";
10  },
11
12  getWinSize() {
13    return this.winSize;
14  },
15};
16
17// 스크롤 방향 체크
18const scrollManager = {
19  _scrollY: 0,
20  _scrollH: 0,
21  _lastScrollY: 0,
22
23  updateScrollValues() {
24    this._scrollY = window.scrollY;
25    this._scrollH = document.body.scrollHeight;
26  },
27
28  handleScrollDirection() {
29    const $wrap = document.querySelector("#wrap");
30    if ($wrap) {
31      const _conOffsetTop = document.querySelector("#container").offsetTop;
32      const _scrollY = window.scrollY;
33      const _scrollDown = _scrollY > this._lastScrollY;
34      const _scrollUp = _scrollY < this._lastScrollY;
35
36      if (_scrollY > _conOffsetTop + 50 && _scrollDown) {
37        $wrap.classList.add("scroll-down");
38        $wrap.classList.remove("scroll-up");
39      } else if (_scrollY > _conOffsetTop + 50 && _scrollUp) {
40        $wrap.classList.add("scroll-up");
41        $wrap.classList.remove("scroll-down");
42      } else {
43        $wrap.classList.remove("scroll-down", "scroll-up");
44      }
45
46      this._lastScrollY = _scrollY;
47    }
48  },
49
50  getscrollY() {
51    return this._scrollY;
52  },
53
54  getScrollH() {
55    return this._scrollH;
56  },
57};
58
59// common
60const common = {
61  focusTrap(trap) {
62    const focusableElements = trap.querySelectorAll(`a, button, [tabindex="0"], input, textarea, select`);
63
64    if (!focusableElements.length) return;
65
66    const firstFocusableElement = focusableElements[0];
67    const lastFocusableElement = focusableElements[focusableElements.length - 1];
68
69    trap.addEventListener("keydown", (event) => {
70      if (event.key === "Tab") {
71        if (event.shiftKey && document.activeElement === firstFocusableElement) {
72          event.preventDefault();
73          lastFocusableElement.focus();
74        } else if (!event.shiftKey && document.activeElement === lastFocusableElement) {
75          event.preventDefault();
76          firstFocusableElement.focus();
77          // 모달 오픈 후 첫 초점 역방향 제어(modal-content가 첫초점이 아니면 사용 안해도 됨)
78        } else if (event.key === "Tab" && event.shiftKey && document.activeElement === trap) {
79          event.preventDefault();
80          lastFocusableElement.focus();
81        }
82      }
83    });
84  },
85};
86
87/*** * krds_mainMenuPC * ***/
88const krds_mainMenuPC = {
89  init() {
90    const gnbMenu = document.querySelector(".krds-main-menu:not(.sample) .gnb-menu");
91
92    if (!gnbMenu) return;
93
94    // gnb 속성설정
95    gnbMenu.setAttribute("aria-label", "메인 메뉴");
96
97    // dimed 요소를 설정, 기존 dimed가 없을 경우 생성
98    this.backdrop = document.querySelector(".gnb-backdrop") || this.createBackdrop();
99
100    // 주 메뉴 및 서브 메뉴의 트리거를 설정하고, 각 트리거에 이벤트를 연결
101    const mainTriggers = gnbMenu.querySelectorAll(".gnb-main-trigger");
102    const subTriggers = gnbMenu.querySelectorAll(".gnb-sub-trigger:not(.is-link)");
103    mainTriggers.forEach((mainTrigger) => this.setupMainTrigger(mainTrigger));
104    this.attachEvents(mainTriggers, subTriggers);
105    this.setupKeyboardNavigation(mainTriggers);
106  },
107  setupMainTrigger(mainTrigger) {
108    const toggleWrap = mainTrigger.nextElementSibling;
109    if (toggleWrap) {
110      const uniqueIdx = `gnb-main-menu-${Math.random().toString(36).substring(2, 9)}`;
111      mainTrigger.setAttribute("aria-controls", uniqueIdx);
112      mainTrigger.setAttribute("aria-expanded", "false");
113      mainTrigger.setAttribute("aria-haspopup", "true");
114      toggleWrap.setAttribute("id", uniqueIdx);
115
116      // 하위 메뉴 설정
117      const mainList = toggleWrap.querySelector(".gnb-main-list");
118      if (mainList?.getAttribute("data-has-submenu") === "true") {
119        const subTriggers = mainList.querySelectorAll(".gnb-sub-trigger");
120        subTriggers.forEach((subTrigger) => this.setupSubTrigger(subTrigger));
121        if (subTriggers.length >
121 0 && !subTriggers[0].classList.contains("is-link")) {
122          subTriggers[0].classList.add("active");
123          subTriggers[0].setAttribute("aria-expanded", "true");
124          subTriggers[0].nextElementSibling?.classList.add("active");
125        }
126      }
127    }
128  },
129  setupSubTrigger(subTrigger) {
130    const hasMenu = subTrigger.nextElementSibling;
131    if (hasMenu) {
132      const uniqueIdx = `gnb-sub-menu-${Math.random().toString(36).substring(2, 9)}`;
133      subTrigger.setAttribute("aria-controls", uniqueIdx);
134      subTrigger.setAttribute("aria-expanded", "false");
135      subTrigger.setAttribute("aria-haspopup", "true");
136      hasMenu.setAttribute("id", uniqueIdx);
137    }
138  },
139  toggleMainMenu(mainTrigger) {
140    const isActive = mainTrigger.classList.contains("active");
141    const isDropDown = mainTrigger.classList.contains("is-dropdown");
142    if (!isActive && mainTrigger.nextElementSibling) {
143      this.resetMainMenu();
144      mainTrigger.setAttribute("aria-expanded", "true");
145      mainTrigger.classList.add("active");
146      mainTrigger.nextElementSibling.classList.add("is-open");
147      if (!isDropDown) {
148        this.toggleBackdrop(true);
149        this.toggleScrollbar(true);
150        this.adjustSubMenuHeight(mainTrigger.nextElementSibling.querySelector(".gnb-main-list"));
151      }
152    } else {
153      this.closeMainMenu();
154    }
155  },
156  toggleSubMenu(subTrigger) {
157    const otherSubTriggers = subTrigger.closest("ul").querySelectorAll(".gnb-sub-trigger:not(.is-link)");
158    otherSubTriggers.forEach((trigger) => {
159      trigger.classList.remove("active");
160      trigger.setAttribute("aria-expanded", "false");
161      trigger.nextElementSibling?.classList.remove("active");
162    });
163    subTrigger.classList.add("active");
164    subTrigger.setAttribute("aria-expanded", "true");
165    subTrigger.nextElementSibling?.classList.add("active");
166    this.adjustSubMenuHeight(subTrigger.closest(".gnb-main-list"));
167  },
168  createBackdrop() {
169    const backdrop = document.createElement("div");
170    backdrop.classList.add("gnb-backdrop");
171    document.body.appendChild(backdrop);
172    // backdrop.style.display = "none";
173    return backdrop;
174  },
175  toggleBackdrop(isOpen) {
176    this.backdrop?.classList.toggle("active", isOpen);
177    document.body.classList.toggle("is-gnb-web", isOpen);
178    // this.backdrop.style.display = isOpen ? "block" : "none";
179  },
180  adjustSubMenuHeight(target) {
181    // 서브 메뉴 높이를 활성 메뉴에 맞춰 조정
182    const activeSubList = target.querySelector(".gnb-sub-list.active");
183    const height = activeSubList?.scrollHeight || 0;
184    target.style.minHeight = `${height}px`;
185  },
186  toggleScrollbar(isEnabled) {
187    const isScrollNeeded = document.body.scrollHeight > window.innerHeight;
188    document.body.classList.toggle("hasScrollY", isEnabled && isScrollNeeded);
189  },
190  resetMainMenu() {
191    document.querySelectorAll(".krds-main-menu:not(.sample) .gnb-main-trigger:not(.is-link)").forEach((mainTrigger) => {
192      mainTrigger.classList.remove("active");
193      mainTrigger.setAttribute("aria-expanded", "false");
194    });
195    document.querySelectorAll(".krds-main-menu:not(.sample) .gnb-toggle-wrap").forEach((toggleWrap) => {
196      toggleWrap.classList.remove("is-open");
197    });
198  },
199  closeMainMenu() {
200    this.resetMainMenu();
201    this.toggleBackdrop(false);
202    this.toggleScrollbar(false);
203  },
204  attachEvents(mainTriggers, subTriggers) {
205    // krds-main-menu 외부 클릭시 닫기
206    document.addEventListener("click", ({ target }) => {
207      if (!target.closest(".krds-main-menu")) this.closeMainMenu();
208    });
209
210    // 백드롭 클릭 시 메뉴 닫기
211    // this.backdrop.addEventListener("click", () => this.closeMainMenu());
212
213    // ESC 키를 눌러 메뉴를 닫거나, TAB 키로 초점이 메뉴 외부로 이동했을 때 메뉴 닫기
214    document.addEventListener("keyup", (event) => {
215      if (event.code === "Escape" || !event.target.closest(".krds-main-menu")) {
216        this.closeMainMenu();
217      }
218    });
219
220    // 메인 메뉴 트리거 설정
221    mainTriggers.forEach((mainTrigger) => {
222      mainTrigger.addEventListener("click", () => this.toggleMainMenu(mainTrigger));
223    });
224
225    // 서브 메뉴 트리거 설정
226    subTriggers.forEach((subTrigger) => {
227      subTrigger.addEventListener("click", () => this.toggleSubMenu(subTrigger));
228    });
229  },
230  setupKeyboardNavigation(mainTriggers) {
231    const focusMenuItem = (element) => {
232      if (element) {
233        element.focus();
234      }
235    };
236    const findFocusableElement = (element, direction) => {
237      const sibling = direction === "next" ? "nextElementSibling" : "previousElementSibling";
238      const parent = element.closest("li")?.[sibling];
239      return parent ? parent.querySelector("[data-trigger]") : null;
240    };
241    // Home, End, 방향키를 통해 메뉴 항목 간의 이동을 처리
242    document.addEventListener("keydown", (event) => {
243      const target = event.target;
244      if (target.getAttribute("data-trigger")) {
245        switch (event.key) {
246          case "Home":
247            event.preventDefault();
248            focusMenuItem(mainTriggers[0]);
249            break;
250          case "End":
251            event.preventDefault();
252            focusMenuItem(mainTriggers[mainTriggers.length - 1]);
253            break;
254          case "ArrowRight":
255          case "ArrowDown":
256            event.preventDefault();
257            const nextElement = findFocusableElement(target, "next");
258            focusMenuItem(nextElement);
259            break;
260          case "ArrowLeft":
261          case "ArrowUp":
262            event.preventDefault();
263            const previousElement = findFocusableElement(target, "prev");
264            focusMenuItem(previousElement);
265            break;
266          default:
267            break;
268        }
269      }
270    });
271  },
272};
273
274/*** * krds_mainMenuMobile * ***/
275const krds_mainMenuMobile = {
276  init() {
277    const mobileGnb = document.querySelector(".krds-main-menu-mobile:not(.sample)");
278
279    if (!mobileGnb) return;
280
281    if (mobileGnb.classList.contains("is-open")) {
282      this.openMainMenu(mobileGnb);
283    } else {
284      mobileGnb.style.display = "none";
285    }
286
287    // gnb 외부 클릭 처리
288    mobileGnb.addEventListener("click", (event) => {
289      if (!event.target.closest(".gnb-wrap")) {
290        mobileGnb.querySelector(".gnb-wrap").focus();
291      }
292    });
293
294    // 접근성 설정(tab)
295    this.setupAriaAttributes(mobileGnb);
296
297    this.attachEvents(mobileGnb);
298  },
299  setupAriaAttributes(mobileGnb) {
300    const tabList = mobileGnb.querySelector(".menu-wrap");
301    if (tabList) {
302      tabList.querySelector(".menu-wrap ul").setAttribute("role", "tablist");
303      tabList.querySelectorAll(".menu-wrap li").forEach((li) => li.setAttribute("role", "none"));
304
305      const tabs = document.querySelectorAll(".menu-wrap .gnb-main-trigger");
306      tabs.forEach((item, idx) => {
307        item.setAttribute("role", "tab");
308        item.setAttribute("aria-selected", "false");
309        item.setAttribute("aria-controls", item.getAttribute("href").substring(1));
310        item.setAttribute("id", `tab-${idx}`);
311
312        // gnb-main-trigger 클릭시 해당 위치로 스크롤
313        item.addEventListener("click", (event) => {
314          event.preventDefault();
315          const id = item.getAttribute("aria-controls");
316          const top = document.getElementById(id).offsetTop
317          const gnbBody = document.querySelector(".gnb-body");
318          gnbBody.scrollTo({
319            left: 0,
320            top: top,
321            behavior: "smooth",
322          });
323        })
324      });
325
326      const tabPanels = document.querySelectorAll(".submenu-wrap .gnb-sub-list");
327      tabPanels.forEach((item, idx) => {
328        item.setAttribute("role", "tabpanel");
329        item.setAttribute("aria-labelledby", `tab-${idx}`);
330      });
331    }
332  },
333  attachEvents(mobileGnb) {
334    const id = mobileGnb.getAttribute("id");
335    const openGnb = document.querySelector(`[aria-controls=${id}]`);
336    const closeGnb = mobileGnb.querySelector("#close-nav");
337
338    openGnb.addEventListener("click", () => this.openMainMenu(mobileGnb));
339    closeGnb.addEventListener("click", () => this.closeMainMenu(mobileGnb));
340    this.setupAnchorScroll(mobileGnb);
341    this.setupAnchorLinks(mobileGnb);
342
343    // 반응형 처리
344    window.addEventListener("resize", () => {
345      const isPC = windowSize.getWinSize() === "pc";
346      if (isPC) this.closeMainMenu(mobileGnb);
347    });
348  },
349  openMainMenu(mobileGnb) {
350    const navContainer = mobileGnb.querySelector(".gnb-wrap");
351    // const id = mobileGnb.getAttribute("id");
352    // const openGnb = document.querySelector(`[aria-controls=${id}]`);
353    // const header = document.querySelector("#krds-header");
354
355    mobileGnb.style.display = "block";
356    navContainer.setAttribute("tabindex", 0);
357    // openGnb.setAttribute("aria-expanded", "true");
358    // header.style.zIndex = "1000";
359
360    // active 메뉴로 스크롤 이동
361    const activeTrigger = document.querySelector(".gnb-main-trigger.active");
362    if (activeTrigger) {
363      const id = activeTrigger.getAttribute("aria-controls");
364      const top = document.getElementById(id).offsetTop;
365      const gnbBody = document.querySelector(".gnb-body");
366      gnbBody.style.scrollBehavior = "auto";
367      gnbBody.scrollTop = top
368    } 
369    
370    setTimeout(() => {
371      mobileGnb.classList.add("is-backdrop");
372      mobileGnb.classList.add("is-open");
373      document.body.classList.add("is-gnb-mobile");
374    }, 100);
375
376    // transition 종료후 실행
377    mobileGnb.addEventListener("transitionend", function onTransitionEnd() {
378      navContainer.focus();
379      mobileGnb.removeEventListener("transitionend", onTransitionEnd);
380
381      // inert 설정
382      document.querySelector("#krds-header .header-in").setAttribute("inert", "");
383      document.getElementById("container")?.setAttribute("inert", "");
384      document.getElementById("footer")?.setAttribute("inert", "");
385      
386      // 포커스 트랩 설정
387      common.focusTrap(mobileGnb);
388    });
389
390  },
391  closeMainMenu(mobileGnb) {
392    const id = mobileGnb.getAttribute("id");
393    const openGnb = document.querySelector(`[aria-controls=${id}]`);
394    // const header = document.querySelector("#krds-header");
395
396    mobileGnb.classList.remove("is-backdrop");
397    mobileGnb.classList.remove("is-open");
398    // openGnb.setAttribute("aria-expanded", "false");
399    // header.style.zIndex = "";
400
401    // inert 설정
402    document.querySelector("#krds-header .header-in").removeAttribute("inert");
403    document.getElementById("container")?.removeAttribute("inert");
404    document.getElementById("footer")?.removeAttribute("inert");
405    
406    // transition 종료후 실행
407    mobileGnb.addEventListener("transitionend", function onTransitionEnd() {
408      openGnb.focus();
409      mobileGnb.removeEventListener("transitionend", onTransitionEnd);
410    });
411    
412    setTimeout(() => {
413      mobileGnb.style.display = "none";
414      document.body.classList.remove("is-gnb-mobile");
415    }, 400);
416  },
417  setupAnchorScroll(mobileGnb) {
418    const gnbBody = mobileGnb.querySelector(".gnb-body");
419    const navContainer = mobileGnb.querySelector(".gnb-wrap");
420    const navItems = mobileGnb.querySelectorAll(".submenu-wrap .gnb-sub-list");
421    const headerTabArea = mobileGnb.querySelector(".gnb-tab-nav");
422    const headerTabMenu = headerTabArea?.querySelector(".menu-wrap");
423
424    gnbBody.addEventListener("scroll", () => {
425      const scrollTop = gnbBody.scrollTop;
426      const scrollHeight = gnbBody.scrollHeight;
427      const bodyHeight = gnbBody.clientHeight;
428
429      navItems.forEach((item) => {
430        const id = item.getAttribute("id");
431        const menuLink = mobileGnb.querySelector(`[href="#${id}"]`);
432        const offset = item.offsetTop;
433        if (scrollTop >= offset || bodyHeight + scrollTop >= scrollHeight) {
434          this.resetAnchorMenu();
435          menuLink.classList.add("active");
436          menuLink.setAttribute("aria-selected", "true");
437          if (headerTabArea) {
438            const headerTabMenuUl = headerTabMenu.querySelector("ul");
439            gnbBody.addEventListener("scrollend", () => {
440              headerTabMenuUl.scrollLeft = menuLink.offsetLeft;
441            });
442          }
443        }
444      });
445
446      this.handleTopScroll(headerTabArea, navContainer, gnbBody);
447    });
448  },
449  handleTopScroll(headerTabArea, navContainer, gnbBody) {
450    // gnb-mobile-type1(headerTabArea: gnb-tab-nav)
451    let lastBodyScrollY = 0;
452
453    if (!headerTabArea) return; // 요소가 없을 경우 함수 종료
454
455    gnbBody.addEventListener("scroll", (event) => {
456      const bodyScrollY = event.target.scrollTop;
457
458      if (bodyScrollY > 0) {
459        headerTabArea.style.height = `${headerTabArea.scrollHeight}px`;
460        headerTabArea.style.transition = "ease-out .4s";
461        navContainer.classList.add("is-active");
462      } else if (bodyScrollY < 50 && bodyScrollY < lastBodyScrollY) {
463        headerTabArea.style.height = "";
464        headerTabArea.style.transition = "ease-out .4s .2s";
465        setTimeout(() => {
466          navContainer.classList.remove("is-active");
467        }, 600);
468      }
469
470      lastBodyScrollY = bodyScrollY;
471    });
472  },
473  setupAnchorLinks(mobileGnb) {
474    const menuItems = mobileGnb.querySelectorAll(".menu-wrap .gnb-main-trigger");
475    const navItems = mobileGnb.querySelectorAll(".submenu-wrap .gnb-sub-list");
476
477    if (!document.querySelector(".menu-wrap .gnb-main-trigger.active")) {
478      menuItems[0].classList.add("active");
479      menuItems[0].setAttribute("aria-selected", "true");
480    }
481
482    // 3depth
483    navItems.forEach((item) => {
484      const depth3Items = item.querySelectorAll(".has-depth3");
485      if (depth3Items.length > 0) {
486        depth3Items.forEach((item) => {
487          if (item.classList.contains("active")) {
488            item.classList.add("active");
489            item.setAttribute("aria-expanded", "true");
490            item.nextElementSibling.classList.add("is-open");
491          } else {
492            item.setAttribute("aria-expanded", "false");
493          }
494          item.addEventListener("click", (event) => this.handleDepth3Click(event, item));
495        });
496      }
497    });
498
499    // 4depth
500    navItems.forEach((item) => {
501      const depth4Items = item.querySelectorAll(".has-depth4");
502      if (depth4Items.length > 0) {
503        depth4Items.forEach((item) => {
504          item.addEventListener("click", (event) => this.handleDepth4Click(event, item));
505        });
506      }
507    });
508  },
509  handleDepth3Click(event) {
510    const isActive = event.target.classList.contains("active");
511
512    const resetList = () => {
513      document.querySelectorAll(".has-depth3").forEach((depth3) => {
514        depth3.classList.remove("active");
515        depth3.setAttribute("aria-expanded", "false");
516        depth3.nextElementSibling.classList.remove("is-open");
517      });
518    };
519
520    if (!isActive) {
521      // resetList();
522      event.target.classList.add("active");
523      event.target.setAttribute("aria-expanded", "true");
524      event.target.nextElementSibling.classList.add("is-open");
525    } else {
526      // resetList();
527      event.target.classList.remove("active");
528      event.target.setAttribute("aria-expanded", "false");
529      event.target.nextElementSibling.classList.remove("is-open");
530    }
531  },
532  handleDepth4Click(event) {
533    const target = event.target.nextElementSibling;
534    const prevButton = target.querySelector(".trigger-prev");
535    const closeButton = target.querySelector(".trigger-close");
536
537    target.style.display = "block";
538    setTimeout(() => {
539      target.classList.add("is-open");
540    }, 0);
541    prevButton.focus();
542
543    const depth4Close = () => {
544      target.classList.remove("is-open");
545      event.target.focus();
546      setTimeout(() => {
547        target.style.display = "";
548      }, 400);
549    };
550
551    prevButton.addEventListener("click", depth4Close);
552    closeButton.addEventListener("click", depth4Close);
553
554    // 포커스 트랩 설정
555    common.focusTrap(target);
556  },
557  resetAnchorMenu() {
558    document.querySelectorAll(".krds-main-menu-mobile .menu-wrap .gnb-main-trigger").forEach((menu) => {
559      menu.classList.remove("active");
560      menu.setAttribute("aria-selected", "false");
561    });
562  },
563};
564
565/*** * krds_sideNavigation * ***/
566const krds_sideNavigation = {
567  init() {
568    this.setupSideNavLists();
569    this.setupToggleEvents();
570    this.setupPopupEvents();
571  },
572  setupSideNavLists() {
573    const sideNavLists = document.querySelectorAll(".krds-side-navigation .lnb-list");
574    sideNavLists.forEach((navList) => {
575      const navItems = navList.querySelectorAll("li");
576      navItems.forEach((navItem) => this.setupNavItem(navItem));
577    });
578  },
579  setupNavItem(navItem) {
580    const navButton = navItem.querySelector(".lnb-btn");
581    if (!navButton || !navButton.className.includes("lnb-toggle")) return;
582
583    const uniqueIdx = `lnbmenu-${Math.random().toString(36).substring(2, 9)}`;
584    const navSubmenu = navButton.nextElementSibling;
585
586    // aria 설정
587    navButton.setAttribute("aria-controls", uniqueIdx);
588    navButton.setAttribute("aria-expanded", navButton.classList.contains("active"));
589
590    // 서브메뉴 id 설정 및 popup 처리
591    if (navButton.classList.contains("lnb-toggle-popup")) {
592      navButton.setAttribute("aria-haspopup", "true");
593    }
594    if (navSubmenu && navSubmenu.className.includes("lnb-submenu")) {
595      const navSubmenuList = navSubmenu.classList.contains("lnb-submenu-lv2") ? navSubmenu : navSubmenu.querySelector(":scope > ul");
596      navSubmenuList?.setAttribute("id", uniqueIdx);
597    }
598  },
599  setupToggleEvents() {
600    const toggleButtons = document.querySelectorAll(".krds-side-navigation .lnb-list:not(.exception-case) .lnb-toggle");
601    toggleButtons.forEach((toggleButton) => {
602      toggleButton.addEventListener("click", () => {
603        const expand = toggleButton.getAttribute("aria-expanded") !== "true";
604        
605        //this.toggleMenu(toggleButton, expand); 클릭 시 해당 버튼만 활성화 상태로 변경되어 해당 이벤트 주석처리.
606        //this.closeSiblingMenus(toggleButton);  클릭 시 해당 버튼만 활성화 상태로 변경되어 해당 이벤트 주석처리.
607        this.setActiveNav(toggleButton, expand);
608      });
609    });
610  },
611  setActiveNav(toggleButton, expand){
612    const parentListItem = toggleButton.closest("li");
613    toggleButton.setAttribute('aria-expanded', expand ? 'true' : 'false');
614    parentListItem.classList.toggle('active', expand);
615  },
616  setupPopupEvents() {
617    let lastClickedButton = null;
618
619    const popupToggleButtons = document.querySelectorAll(".lnb-toggle-popup");
620    const popupSubmenus = document.querySelectorAll(".lnb-submenu-lv2");
621
622    // 팝업 토글 버튼
623    popupToggleButtons.forEach((button) => {
624      button.addEventListener("click", () => {
625        const popupSubmenu = button.nextElementSibling;
626        if (popupSubmenu && popupSubmenu.classList.contains("lnb-submenu-lv2")) {
627          popupSubmenu.classList.add("active");
628          button.setAttribute("aria-expanded", "true");
629
630          popupSubmenu.addEventListener(
631            "transitionend",
632            () => {
633              popupSubmenu.querySelector(".lnb-btn-tit")?.focus();
634            },
635            { once: true }
636          );
637
638          lastClickedButton = button;
639        }
640      });
641    });
642
643    // 서브 메뉴가 포커스를 잃으면 비활성화
644    popupSubmenus.forEach((popupSubmenu) => {
645      popupSubmenu.addEventListener("focusout", (event) => {
646        // 포커스가 서브메뉴 밖으로 나갔는지 확인
647        if (!popupSubmenu.contains(event.relatedTarget)) {
648          popupSubmenu.classList.remove("active");
649
650          if (lastClickedButton) {
651            lastClickedButton.setAttribute("aria-expanded", "false");
652            popupSubmenu.addEventListener(
653              "transitionend",
654              () => {
655                lastClickedButton.focus();
656              },
657              { once: true }
658            );
659          }
660        }
661      });
662
663      // lnb-btn-tit 클릭 시 서브메뉴 닫기
664      const subMenuTitleButton = popupSubmenu.querySelector(".lnb-btn-tit");
665      subMenuTitleButton?.addEventListener("click", () => {
666        lastClickedButton?.focus();
667      });
668    });
669  },
670  toggleMenu(toggleButton, expand) {
671    const parentListItem = toggleButton.closest("li");
672    toggleButton.setAttribute("aria-expanded", expand);
673    toggleButton.classList.toggle("active", expand);
674    parentListItem.classList.toggle("active", expand);
675  },
676  closeSiblingMenus(toggleButton) {
677    const parentListItem = toggleButton.closest("li");
678    const siblingButtons = parentListItem.parentNode.querySelectorAll(":scope > li > .lnb-toggle");
679    siblingButtons.forEach((siblingButton) => {
680      if (siblingButton !== toggleButton) {
681        this.toggleMenu(siblingButton, false); 
682      }
683    });
684  },
685  setActiveCurrentPage() {
686    // 활성화된 페이지를 찾는 예(개발 환경에 맞게 수정)
687    const currentPage = window.location.pathname.split("/").slice(-1)[0].replace(".html", "");
688    const lnbLinks = document.querySelectorAll(".krds-side-navigation .lnb-link");
689    lnbLinks.forEach((link) => {
690      const linkPage = link.getAttribute("href").split("/").pop().replace(".html", "");
691      if (linkPage === currentPage) {
692        link.closest(".lnb-item").classList.add("active");
693        link.closest(".lnb-item").querySelector(".lnb-toggle")?.classList.add("active");
694        link.closest(".lnb-item").querySelector(".lnb-toggle")?.setAttribute("aria-expanded", "true");
695        link.closest("li").classList.add("active");
696        // 접근성 현재 페이지 표시 aria-current
697        link.setAttribute("aria-current", "page");
698      }
699    });
700  },
701};
702
703/*** * krds_tab * ***/
704const krds_tab = {
705  layerTabArea: null,
706  init() {
707    this.layerTabArea = document.querySelectorAll(".krds-tab-area.layer");
708
709    if (!this.layerTabArea.length) return;
710
711    this.setupTabs();
712  },
713  setupTabs() {
714    this.layerTabArea.forEach((tabArea) => {
715      const layerTabs = tabArea.querySelectorAll(".tab > ul > li");
716
717      // 탭 설정
718      layerTabs.forEach((tab) => {
719        // 이미 이벤트가 연결된 탭을 건너뜀
720        if (!tab.dataset.listenerAttached) {
721          // 연결된 탭 패널 찾기
722          const control = tab.getAttribute("aria-controls");
723          const selectedTabPanel = document.getElementById(control);
724
725          // aria 설정
726          tab.setAttribute("aria-selected", "false");
727          tab.setAttribute("role", "tab");
728          selectedTabPanel.setAttribute("role", "tabpanel");
729
730          // 초기 active 설정
731          if (tab.classList.contains("active")) {
732            if (!tab.querySelector("button .sr-only")) {
733              tab.setAttribute("aria-selected", "true");
734              tab.querySelector("button").append(this.createAccText()); // 초점이 버튼이라 aria-selected 대체 텍스트 필요
735            }
736          }
737
738          // 클릭 이벤트
739          tab.addEventListener("click", () => {
740            const closestTabs = tab.closest(".krds-tab-area.layer > .tab").querySelectorAll("li");
741            const closestTabPanels = tab.closest(".krds-tab-area.layer").querySelectorAll(":scope > .tab-conts-wrap > .tab-conts");
742
743            this.resetTabs(closestTabs, closestTabPanels);
744
745            tab.classList.add("active");
746            tab.querySelector("button").append(this.createAccText());
747            tab.setAttribute("aria-selected", "true");
748            selectedTabPanel.classList.add("active");
749          });
750
751          // 키보드 이벤트
752          this.setupKeyboardNavigation(tab);
753
754          // 이벤트가 추가된 탭을 표시
755          tab.dataset.listenerAttached = "true";
756        }
757      });
758    });
759  },
760  createAccText() {
761    const tabAccTag = document.createElement("i");
762    tabAccTag.classList.add("sr-only");
763    tabAccTag.textContent = "선택됨";
764    return tabAccTag;
765  },
766  resetTabs(closestTabs, closestTabPanels) {
767    closestTabs.forEach((tab) => {
768      tab.classList.remove("active");
769      tab.setAttribute("aria-selected", "false");
770      // 대체 텍스트 삭제
771      const srOnly = tab.querySelector(".sr-only");
772      if (srOnly) tab.querySelector("button").removeChild(srOnly);
773    });
774    closestTabPanels.forEach((panel) => {
775      panel.classList.remove("active");
776    });
777  },
778  setupKeyboardNavigation(tab) {
779    tab.addEventListener("keydown", function (event) {
780      let newTab;
781      if (event.key === "ArrowRight") {
782        event.preventDefault();
783        newTab = tab.nextElementSibling?.querySelector("button");
784      } else if (event.key === "ArrowLeft") {
785        newTab = tab.previousElementSibling?.querySelector("button");
786      }
787      newTab?.focus();
788    });
789  },
790};
791
792/*** * krds_accordion * ***/
793const krds_accordion = {
794  accordionButtons: null,
795  accordionHandlers: new Map(),
796  init() {
797    this.accordionButtons = document.querySelectorAll(".btn-accordion");
798
799    if (!this.accordionButtons.length) return;
800
801    this.setupAccordions();
802  },
803  accordionToggle(button, accordionItems, accordionType, currentItem) {
804    const isExpanded = button.getAttribute("aria-expanded") === "true";
805    // singleOpen 타입일 경우, 다른 항목 닫기
806    if (accordionType !== "multiOpen" && !currentItem.classList.contains("active")) {
807      accordionItems.forEach((otherItem) => {
808        const otherButton = otherItem.querySelector(".btn-accordion");
809        otherButton.setAttribute("aria-expanded", "false");
810        otherButton.classList.remove("active");
811        otherItem.classList.remove("active");
812      });
813    }
814    // 현재 항목 상태 토글
815    button.setAttribute("aria-expanded", !isExpanded);
816    button.classList.toggle("active", !isExpanded);
817    currentItem.classList.toggle("active", !isExpanded);
818  },
819  setupAccordions() {
820    this.accordionButtons.forEach((button, idx) => {
821      const accordionContainer = button.closest(".krds-accordion");
822      const accordionItems = accordionContainer.querySelectorAll(".accordion-item");
823      const currentItem = button.closest(".accordion-item");
824      const accordionContent = currentItem.querySelector(".accordion-collapse");
825      const accordionType = accordionContainer.dataset.type || "singleOpen";
826      const isOpen = accordionContainer.classList.contains("is-open");
827
828      // 접근성 속성 초기값 설정
829      this.setupAriaAttributes(button, accordionContent, idx);
830
831      // 초기 오픈 상태 설정
832      if (isOpen || currentItem.classList.contains("active")) {
833        button.setAttribute("aria-expanded", "true");
834        button.classList.add("active");
835        currentItem.classList.add("active");
836      }
837
838      // 핸들러 고정 및 저장
839      let toggleHandler = this.accordionHandlers.get(button);
840      if (!toggleHandler) {
841        toggleHandler = this.accordionToggle.bind(this, button, accordionItems, accordionType, currentItem);
842        this.accordionHandlers.set(button, toggleHandler);
843      }
844
845      // 기존 이벤트 리스너 제거 및 새로 등록
846      button.removeEventListener("click", toggleHandler);
847      button.addEventListener("click", toggleHandler);
848    });
849  },
850  setupAriaAttributes(button, accordionContent, idx) {
851    const uniqueIdx = `${idx}${Math.random().toString(36).substring(2, 9)}`;
852    button.setAttribute("aria-expanded", "false");
853    button.setAttribute("id", `accordionHeader-id-${uniqueIdx}`);
854    button.setAttribute("aria-controls", `accordionCollapse-id-${uniqueIdx}`);
855    accordionContent.setAttribute("role", "region");
856    accordionContent.setAttribute("id", `accordionCollapse-id-${uniqueIdx}`);
857    accordionContent.setAttribute("aria-labelledby", `accordionHeader-id-${uniqueIdx}`);
858  },
859};
860
861/*** * krds_modal * ***/
862const krds_modal = {
863  modalOpenTriggers: null,
864  modalCloseTriggers: null,
865  outsideClickHandlers: {},
866  init() {
867    this.modalOpenTriggers = document.querySelectorAll(".open-modal");
868    this.modalCloseTriggers = document.querySelectorAll(".close-modal");
869
870    if (!this.modalOpenTriggers.length || !this.modalCloseTriggers.length) return;
871
872    this.setupTriggers();
873  },
874  setupTriggers() {
875    // 모달 열기 이벤트 설정
876    this.modalOpenTriggers.forEach((trigger) => {
877      trigger.addEventListener("click", (event) => {
878        event.preventDefault();
879        const modalId = trigger.getAttribute("data-target");
880        if (modalId) {
881          // aria 설정
882          trigger.setAttribute("data-modal-id", modalId);
883          trigger.classList.add("modal-opened");
884          trigger.setAttribute("tabindex", "-1");
885			
886          this.openModal(modalId);
887        }
888      });
889    });
890    // 모달 닫기 이벤트 설정
891    this.modalCloseTriggers.forEach((trigger) => {
892      trigger.addEventListener("click", (event) => {
893        event.preventDefault();
894        const modalId = trigger.closest(".krds-modal").getAttribute("id");
895
896        if (modalId) {
897          this.closeModal(modalId);
898        }
899      });
900    });
901  },
902  openModal(id) {
903    const modalElement = document.getElementById(id);
904    const dialogElement = modalElement.querySelector(".modal-content");
905    const modalBack = modalElement.querySelector(".modal-back");
906    // const modalTitle = modalElement.querySelector(".modal-title");
907    const modalConts = modalElement.querySelector(".modal-conts");
908
909    document.querySelector("body").classList.add("scroll-no");
910    dialogElement.removeAttribute("tabindex");
911    modalElement.setAttribute("role", "dialog");
912    modalElement.classList.add("shown");
913    modalBack.classList.add("in");
914    // modalTitle.setAttribute("tabindex", "0");
915
916    // modal-conts 스크롤 일때 tabindex 처리
917    if (modalConts.scrollHeight > modalConts.clientHeight) {
918      modalConts.setAttribute("tabindex", "0");
919    } else {
920      modalConts.removeAttribute("tabindex");
921    }
922
923    // css transition 딜레이
924    setTimeout(() => {
925      modalElement.classList.add("in");
926    }, 150);
927    
928    //열린 팝업창 포커스
929    const focusables = modalElement.querySelectorAll(`a, button, [tabindex="0"], input, textarea, select`);
930    setTimeout(() => {
931      // modalTitle.focus();
932      focusables[0].focus();
933    }, 350);
934
935    // ESC 모달 닫기
936    dialogElement.addEventListener(
937      "keydown",
938      (event) => {
939        if (event.key === "Escape" || event.key === "Esc") {
940          this.closeModal(dialogElement.closest(".krds-modal").id
940);
941        }
942      },
943      { once: true }
944    );
945
946    // 모달 외부 클릭 처리 핸들러 정의 및 저장
947    if (!this.outsideClickHandlers[id]) {
948      this.outsideClickHandlers[id] = (event) => {
949        if (!event.target.closest(".modal-content")) {
950          // modalTitle.focus();
951          focusables[0].focus();
952
953          // dialogElement.focus();
954          // this.closeModal(id);
955        }
956      };
957    }
958    // 이벤트 리스너 제거 후 다시 등록
959    modalElement.removeEventListener("click", this.outsideClickHandlers[id]);
960    modalElement.addEventListener("click", this.outsideClickHandlers[id]);
961
962    // 포커스 트랩 설정
963    common.focusTrap(dialogElement);
964
965    // 2개 이상의 모달이 열려 있는 경우 z-index 업데이트
966    this.updateZIndex(modalElement);
967
968    // inert 설정
969    document.getElementById("wrap")?.setAttribute("inert", "");
970  },
971  closeModal(id) {
972    const modalElement = document.getElementById(id);
973    const openModals = document.querySelectorAll(".modal.in:not(.sample)");
974    const modalBack = modalElement.querySelector(".modal-back");
975
976    modalElement.classList.remove("in");
977    modalBack.classList.remove("in");
978
979    // css transition 딜레이
980    setTimeout(() => {
981      modalElement.classList.remove("shown");
982    }, 350);
983
984    // 마지막 모달이 닫힐 때 페이지 스크롤 복원
985    if (openModals.length < 2) {
986      document.querySelector("body").classList.remove("scroll-no");
987    }
988    
989    // inert 설정
990    document.getElementById("wrap")?.removeAttribute("inert");
991
992    // 모달을 열었던 버튼으로 포커스 복귀
993    this.returnFocusToTrigger(id);
994  },
995  updateZIndex(modalElement) {
996    const openModals = document.querySelectorAll(".modal.in:not(.sample)");
997    const openModalsLengtn = openModals.length + 1;
998    const newZIndex = 1010 + openModalsLengtn;
999    if (openModalsLengtn > 1) {
1000      modalElement.style.zIndex = newZIndex;
1001      modalElement.querySelector(".modal-back").classList.remove("in");
1002    }
1003  },
1004  returnFocusToTrigger(id) {
1005    const triggerButton = document.querySelector(`.modal-opened[data-modal-id="${id}"]`);
1006    if (triggerButton) {
1007      triggerButton.focus();
1008      triggerButton.setAttribute("tabindex", "0");
1009      triggerButton.classList.remove("modal-opened");
1010      triggerButton.removeAttribute("data-modal-id");
1011    }
1012  },
1013};
1014
1015/*** * krds_contextualHelp * ***/
1016const krds_contextualHelp = {
1017  tooltipButtons: null,
1018  init() {
1019    this.tooltipButtons = document.querySelectorAll(".krds-contextual-help .tooltip-btn");
1020
1021    if (!this.tooltipButtons.length) return;
1022
1023    this.setupTooltips();
1024    this.setupFocusOutEvent();
1025  },
1026  setupTooltips() {
1027    this.tooltipButtons.forEach((button) => {
1028      const tooltipContainer = button.closest(".krds-contextual-help");
1029      const tooltipPopover = tooltipContainer.querySelector(".tooltip-popover");
1030      const closeButton = tooltipPopover.querySelector(".tooltip-close");
1031
1032      button.setAttribute("aria-expanded", "false");
1033      tooltipPopover.setAttribute("role", "tooltip");
1034
1035      // tooltipWrap에 포지션이 없을때 기본값 설정
1036      if (tooltipContainer && tooltipContainer.classList.length === 1) {
1037        tooltipContainer.classList.add("top", "left");
1038      }
1039
1040      button.addEventListener("click", () => {
1041        this.toggleTooltip(button, tooltipPopover, tooltipContainer);
1042      });
1043      closeButton.addEventListener("click", () => {
1044        this.closeAllTooltips();
1045      });
1046
1047      window.addEventListener("resize", () => {
1048        this.adjustTooltipPosition(tooltipContainer, tooltipPopover);
1049      });
1050
1051      // ESC 닫기
1052      document.addEventListener("keydown", (event) => {
1053        if (event.key === "Escape" || event.key === "Esc") {
1054          this.closeAllTooltips();
1055        }
1056      });
1057    });
1058  },
1059  toggleTooltip(button, tooltipPopover, tooltipContainer) {
1060    const isVisible = tooltipPopover.style.display === "block";
1061
1062    this.closeAllTooltips();
1063
1064    if (!isVisible) {
1065      tooltipPopover.style.display = "block";
1066      const focusables = tooltipPopover.querySelector(`a, button, [tabindex="0"], input, textarea, select`);
1067      focusables?.focus();
1068      button.setAttribute("aria-expanded", "true");
1069
1070      this.adjustTooltipPosition(tooltipContainer, tooltipPopover);
1071    }
1072  },
1073  closeAllTooltips() {
1074    const otherPopovers = document.querySelectorAll(".krds-contextual-help .tooltip-popover");
1075    otherPopovers.forEach((popover) => {
1076      popover.style.display = "none";
1077    });
1078    this.tooltipButtons.forEach((button) => {
1079      button.setAttribute("aria-expanded", "false");
1080    });
1081  },
1082  adjustTooltipPosition(tooltipContainer, tooltipPopover) {
1083    // const isMobile = windowSize.getWinSize() === "mob";
1084    const isMobile = window.innerWidth <= 768;
1085    const tooltipAction = tooltipContainer.querySelector(".tooltip-action");
1086
1087    if (isMobile) {
1088      const rootStyles = getComputedStyle(document.querySelector(":root"));
1089      const contentsPaddingX = rootStyles.getPropertyValue("--krds-contents-padding-x").trim().split("px")[0];
1090      const tooltipActionRect = tooltipAction.getBoundingClientRect();
1091      const offsetLeft = tooltipActionRect.left - contentsPaddingX;
1092      const width = document.body.clientWidth - (contentsPaddingX * 2);
1093      tooltipPopover.style.left = `-${offsetLeft}px`;
1094      tooltipPopover.style.width = `${width}px`;
1095    } else {
1096      tooltipPopover.style.left = "";
1097      tooltipPopover.style.right = "";
1098      tooltipPopover.style.width = "360px";
1099    }
1100  },
1101  setupFocusOutEvent() {
1102    document.addEventListener("click", (event) => {
1103      const clickedInsideTooltip = event.target.closest(".tooltip-action");
1104      if (!clickedInsideTooltip ) {
1105        this.closeAllTooltips();
1106      } else {
1107        const FocusPopover = clickedInsideTooltip.querySelector('.tooltip-close');
1108        FocusPopover.addEventListener('focusout', ()=>{
1109          this.closeAllTooltips();
1110          clickedInsideTooltip.querySelector(".tooltip-btn")?.focus();
1111        });
1112      }
1113    });
1114  },
1115};
1116
1117/*** * krds_tooltip * ***/
1118const krds_tooltip = {
1119  tooltip: null,
1120  isMobile: null,
1121  init() {
1122    this.tooltip = document.querySelectorAll(".krds-tooltip");
1123    this.isMobile = windowSize.getWinSize() === "mob";
1124
1125    if (!this.tooltip.length) return;
1126
1127    this.setupTooltips();
1128    this.setupGlobalEvents();
1129  },
1130  setupTooltips() {
1131    this.tooltip.forEach((item, index) => {
1132      //  tooltipText
1133      const tooltipText = item.getAttribute("data-tooltip");
1134      const disabled = item.hasAttribute("disabled");
1135
1136      if (!tooltipText || disabled) return;
1137
1138      // ID 부여
1139      const uniqueIdx = `tooltip-popover-${index}${Math.random().toString(36).substring(2, 9)}`;
1140      item.setAttribute("aria-labelledby", uniqueIdx);
1141
1142      // TooltipPopover 생성
1143      const tooltipBtnText = item.innerText;
1144      const tooltipPopover = this.createTooltipPopover(uniqueIdx, tooltipBtnText, tooltipText);
1145      item.parentNode.insertBefore(tooltipPopover, item.nextSibling);
1146
1147      // Show/Hide 함수 정의
1148      const showTooltip = () => this.showTooltip(item, tooltipPopover);
1149
1150      // 이벤트 등록
1151      this.registerEvents(item, showTooltip);
1152
1153      // ESC 닫기
1154      document.addEventListener("keydown", (event) => {
1155        if (event.key === "Escape" || event.key === "Esc") {
1156          this.closeAllTooltips();
1157        }
1158      });
1159    });
1160  },
1161  createTooltipPopover(uniqueIdx, tooltipBtnText, tooltipText) {
1162    const tooltipPopover = document.createElement("div");
1163    tooltipPopover.classList.add("krds-tooltip-popover");
1164    tooltipPopover.setAttribute("id", uniqueIdx);
1165    tooltipPopover.setAttribute("aria-hidden", "true");
1166    tooltipPopover.innerHTML = "";
1167    tooltipPopover.innerHTML = `
1168      <span class="sr-only">${tooltipBtnText}</span>
1169      ${tooltipText}
1170    `;
1171
1172    return tooltipPopover;
1173  },
1174  registerEvents(item, showTooltip) {
1175    item.addEventListener("mouseover", showTooltip);
1176    item.addEventListener("mouseout", this.closeAllTooltips);
1177    item.addEventListener("focus", showTooltip);
1178    item.addEventListener("focusout", this.closeAllTooltips);
1179  },
1180  showTooltip(item, tooltipPopover) {
1181    // tooltip-box
1182    if (item.classList.contains("tooltip-box")) {
1183      tooltipPopover.classList.add("tooltip-box");
1184    }
1185    // tooltip-vertical
1186    if (item.classList.contains("tooltip-vertical")) {
1187      tooltipPopover.classList.add("tooltip-vertical");
1188    }
1189
1190    tooltipPopover.classList.add("active");
1191
1192    const { top, left } = this.calculateTooltipPosition(item, tooltipPopover);
1193    const mobileSmall = window.innerWidth <= 420;
1194    tooltipPopover.style.top = `${top}px`;
1195    tooltipPopover.style.left = mobileSmall ? "50%" : `${left}px`;
1196  },
1197  closeAllTooltips() {
1198    const otherPopovers = document.querySelectorAll(".krds-tooltip-popover");
1199    otherPopovers.forEach((popover) => {
1200      if (!popover.classList.contains("active")) return;
1201      popover.style = "";
1202      popover.className = "krds-tooltip-popover";
1203    });
1204  },
1205  calculateTooltipPosition(item, tooltipPopover) {
1206    // 툴팁과 기준 요소 간격
1207    const tooltipGap = 12;
1208    const { clientHeight: tooltipHeight, clientWidth: tooltipWidth } = tooltipPopover;
1209    const { top: itemTop, left: itemLeft, right: itemRight, height: itemHeight, width: itemWidth } = item.getBoundingClientRect();
1210    const halfWindowWidth = window.innerWidth / 2;
1211    const halfWindowHeight = window.innerHeight / 2;
1212
1213    let tooltipTop;
1214    let tooltipLeft;
1215
1216    const isVertical = this.isMobile || item.classList.contains("tooltip-box") || item.classList.contains("tooltip-vertical");
1217
1218    if (isVertical) {
1219      if (itemTop + itemHeight > halfWindowHeight) {
1220        tooltipTop = itemTop - tooltipHeight - tooltipGap; // 위쪽
1221        tooltipPopover.classList.add("top");
1222      } else {
1223        tooltipTop = itemTop + itemHeight + tooltipGap; // 아래쪽
1224        tooltipPopover.classList.add("bottom");
1225      }
1226      // 좌우 위치
1227      if (itemLeft + itemWidth > halfWindowWidth) {
1228        tooltipLeft = itemRight - tooltipWidth; // 오른쪽 정렬
1229        tooltipPopover.classList.add("right");
1230        // 화면 오른쪽 여유 공간이 충분할 때 가운데 정렬
1231        if (window.innerWidth - (itemLeft + itemWidth) > tooltipWidth / 2) {
1232          tooltipLeft = itemLeft + (itemWidth - tooltipWidth) / 2;
1233          tooltipPopover.classList.remove("right");
1234        }
1235      } else {
1236        // 화면 왼쪽 여유 공간이 충분할 때 가운데 정렬
1237        tooltipLeft = itemLeft + (itemWidth - tooltipWidth) / 2;
1238        // 왼쪽 공간 부족 시 보정
1239        if (tooltipLeft < 0) {
1240          tooltipLeft = itemLeft;
1241          tooltipPopover.classList.add("left");
1242        } else {
1243          tooltipPopover.classList.remove("left");
1244        }
1245      }
1246    } else {
1247      // 가로형 툴팁
1248      tooltipTop = itemTop + (itemHeight - tooltipHeight) / 2;
1249      if (itemLeft + itemWidth > halfWindowWidth) {
1250        tooltipLeft = itemLeft - tooltipWidth - tooltipGap; // 왼쪽
1251        tooltipPopover.classList.add("right");
1252      } else {
1253        tooltipLeft = itemRight + tooltipGap; // 오른쪽
1254        tooltipPopover.classList.remove("right");
1255      }
1256    }
1257    return { top: tooltipTop, left: tooltipLeft };
1258  },
1259  setupGlobalEvents() {
1260    window.addEventListener("scroll", () => {
1261      this.closeAllTooltips();
1262    });
1263    window.addEventListener("resize", () => {
1264      this.isMobile = windowSize.getWinSize() === "mob";
1265      this.closeAllTooltips();
1266    });
1267  },
1268};
1269
1270/*** * krds_calendar * ***/ // *btn-set-date: aria-pressed > aria-selected 변경 해야함(상위 속성도 같이)
1271const krds_calendar = {
1272  datePickerArea: null,
1273  activeDateSelector: null,
1274  init() {
1275    this.datePickerArea = document.querySelectorAll(".krds-calendar-area");
1276
1277    if (!this.datePickerArea.length) return;
1278
1279    this.setupDatePicker();
1280    this.setupGlobalListeners();
1281    this.setupOpenCloseEvents();
1282    this.setupDateComboBox("init");
1283    this.toggleDateSelector();
1284    this.actionDatePicker();
1285
1286    // ui 동작 확인용 테스트 코드
1287    this.example();
1288  },
1289  setupDatePicker() {
1290    this.datePickerArea.forEach((datePicker) => {
1291      datePicker.querySelector(".calendar-wrap").setAttribute("tabindex", "0");
1292
1293      // 접근성 초기값 설정
1294      datePicker.querySelectorAll(".calendar-tbl td").forEach((cell) => {
1295        const dateButton = cell.querySelector(".btn-set-date");
1296        if (!dateButton) return;
1297        if (cell.classList.contains("period")) {
1298          dateButton.setAttribute("aria-pressed", "true");
1299        }
1300        if (cell.classList.contains("day-event")) {
1301          dateButton.setAttribute("aria-label", `${dateButton.innerText} 일정있음`);
1302        }
1303        if (cell.classList.contains("today")) {
1304          dateButton.setAttribute("aria-label", `${dateButton.innerText} 오늘`);
1305        }
1306      });
1307    });
1308  },
1309  setupGlobalListeners() {
1310    document.addEventListener("click", (event) => {
1311      if (!event.target.closest(".calendar-conts")) {
1312        this.closeAllDatePickers();
1313      }
1314    });
1315  },
1316  setupOpenCloseEvents() {
1317    const datePickerButtons = document.querySelectorAll(".form-btn-datepicker");
1318    datePickerButtons.forEach((button) => {
1319      button.addEventListener("click", () => this.openDatePicker(button));
1320    });
1321
1322    // 탭 이동 닫기
1323    this.datePickerArea.forEach((datePicker) => {
1324      // 마지막 버튼이 blur될 때 datepicker 닫기(포커스 트랩을 사용하지 않을때 적용)
1325      // const lastActionButton = datePicker.querySelector(".calendar-btn-wrap .krds-btn:last-child");
1326      // if (lastActionButton) {
1327      //   lastActionButton.addEventListener("blur", () => this.closeAllDatePickers());
1328      // }
1329
1330      // calendar-select의 마지막 버튼이 blur될 때 선택기 닫기
1331      datePicker.querySelectorAll(".calendar-select").forEach((select) => {
1332        const lastSelectButton = select.querySelector(".sel > li:last-child > button");
1333        if (lastSelectButton) {
1334          lastSelectButton.addEventListener("blur", () => {
1335            this.resetDateSelector();
1336          });
1337        }
1338      });
1339    });
1340  },
1341  openDatePicker(button) {
1342    // 열려있던 달력 모두 닫기
1343    this.closeAllDatePickers();
1344
1345    const currentDatePicker = button.closest(".calendar-conts").querySelector(".krds-calendar-area");
1346    currentDatePicker.classList.add("active");
1347
1348    // 접근성
1349    button.setAttribute("aria-expanded", "true");
1350
1351    // 포커스 트랩 설정
1352    common.focusTrap(currentDatePicker);
1353
1354    // 포커스 이동
1355    setTimeout(() => {
1356      currentDatePicker.querySelector(".calendar-wrap").focus();
1357    }, 50);
1358  },
1359  resetDateSelector() {
1360    this.datePickerArea.forEach((datePicker) => {
1361      const selectMenus = datePicker.querySelectorAll(".calendar-select");
1362      selectMenus.forEach((select) => select.classList.remove("active"));
1363      this.setupDateComboBox("reset");
1364      this.activeDateSelector = null;
1365    });
1366  },
1367  closeAllDatePickers() {
1368    this.datePickerArea.forEach((datePicker) => {
1369      if (datePicker.classList.contains("active")) {
1370        const target = datePicker.closest(".calendar-conts").querySelector(".form-btn-datepicker");
1371        target.focus();
1372      }
1373      datePicker.classList.remove("active");
1374      this.resetDateSelector();
1375    });
1376
1377    // 접근성
1378    const datePickerButtons = document.querySelectorAll(".form-btn-datepicker");
1379    datePickerButtons.forEach((button) => {
1380      button.setAttribute("aria-expanded", "false");
1381    });
1382  },
1383  setupDateComboBox(option, target) {
1384    const comboBoxs = document.querySelectorAll(".calendar-drop-down > button");
1385    if (option === "init") {
1386      comboBoxs.forEach((comboBox, idx) => {
1387        const listBox = comboBox.nextElementSibling.querySelector(".sel");
1388        const listOptions = listBox.querySelectorAll("button");
1389        const uniqueIdx = `${idx}${Math.random().toString(36).substring(2, 9)}`;
1390
1391        comboBox.setAttribute("role", "combobox");
1392        comboBox.setAttribute("aria-haspopup", "listbox");
1393        comboBox.setAttribute("aria-expanded", "false");
1394        comboBox.setAttribute("aria-controls", `combo-list-${uniqueIdx}`);
1395
1396        listBox.setAttribute("role", "listbox");
1397        listBox.setAttribute("id", `combo-list-${uniqueIdx}`);
1398        listBox.querySelectorAll("li").forEach((li) => li.setAttribute("role", "none"));
1399
1400        listOptions.forEach((listOption) => {
1401          listOption.setAttribute("role", "option");
1402          listOption.setAttribute("aria-selected", "false");
1403          if (listOption.classList.contains("active")) {
1404            listOption.setAttribute("aria-selected", "true");
1405            comboBox.innerHTML = listOption.innerHTML;
1406          }
1407        });
1408      });
1409    }
1410    if (option === "reset") {
1411      comboBoxs.forEach((comboBox) => {
1412        comboBox.setAttribute("aria-expanded", "false");
1413      });
1414      if (target) {
1415        target.setAttribute("aria-expanded", "true");
1416      }
1417    }
1418    if (option === "change") {
1419      comboBoxs.forEach((comboBox) => {
1420        const listBox = comboBox.nextElementSibling.querySelector(".sel");
1421        const listOptions = listBox.querySelectorAll("button");
1422
1423        comboBox.setAttribute("aria-expanded", "false");
1424
1425        listOptions.forEach((listOption) => {
1426          listOption.classList.remove("active");
1427          listOption.setAttribute("aria-selected", "false");
1428        });
1429      });
1430      if (target) {
1431        target.setAttribute("aria-selected", "true");
1432        target.classList.add("active");
1433        target.closest(".calendar-drop-down").querySelector("button").innerHTML = target.innerHTML;
1434      }
1435
1436      // ui 동작 확인용 테스트 코드
1437      this.example();
1438    }
1439  },
1440  toggleDateSelector() {
1441    this.datePickerArea.forEach((datePicker) => {
1442      const selectToggleButtons = datePicker.querySelectorAll(".calendar-drop-down .btn-cal-switch");
1443      const selectOptions = datePicker.querySelectorAll(".calendar-select .sel button");
1444
1445      // 공통 이벤트 처리
1446      const handleBtnClick = (event, selectMenu) => {
1447        const layer = selectMenu;
1448
1449        // 이미 활성화된 레이어 닫기
1450        if (this.activeDateSelector === layer) {
1451          layer.classList.remove("active");
1452          this.setupDateComboBox("reset");
1453          this.activeDateSelector = null;
1454          return;
1455        }
1456
1457        // 현재 열려 있는 레이어가 있으면 닫음
1458        if (this.activeDateSelector) {
1459          this.activeDateSelector.classList.remove("active");
1460          this.setupDateComboBox("reset");
1461        }
1462
1463        // 클릭한 버튼에 해당하는 레이어 열기
1464        layer.classList.add("active");
1465        this.setupDateComboBox("reset", event.target);
1466        this.activeDateSelector = layer;
1467      };
1468
1469      // 셀렉터 설정
1470      selectToggleButtons.forEach((toggle) => {
1471        toggle.addEventListener("click", (event) => {
1472          const selectMenu = event.target.closest(".calendar-drop-down").querySelector(".calendar-select");
1473          handleBtnClick(event, selectMenu);
1474        });
1475      });
1476
1477      // 년도, 월 선택
1478      selectOptions.forEach((option) => {
1479        option.addEventListener("click", (event) => {
1480          this.resetDateSelector();
1481          this.setupDateComboBox("change", event.target);
1482
1483          // 포커스 이동
1484          setTimeout(() => {
1485            option.closest(".calendar-drop-down").querySelector(".btn-cal-switch")?.focus();
1486          }, 50);
1487        });
1488      });
1489
1490      // esc 닫기
1491      datePicker.addEventListener("keydown", (event) => {
1492        if (event.code === "Escape") {
1493          this.resetDateSelector();
1494        }
1495      });
1496    });
1497  },
1498  actionDatePicker() {
1499    this.datePickerArea.forEach((datePicker) => {
1500      const actionButtons = datePicker.querySelectorAll(".calendar-btn-wrap button:not(#get-today)");
1501      actionButtons.forEach((button) => {
1502        button.addEventListener("click", () => {
1503          this.closeAllDatePickers();
1504        });
1505      });
1506    });
1507  },
1508  // ui 동작 확인용 테스트 코드
1509  example() {
1510    this.datePickerArea.forEach((datePicker) => {
1511      const year = datePicker.querySelector(".calendar-switch-wrap .year").innerText.slice(0, -1);
1512      const month = datePicker.querySelector(".calendar-switch-wrap .month").innerText.slice(0, -1);
1513      const caption = datePicker.querySelector(".calendar-tbl caption");
1514      const tblCells = datePicker.querySelectorAll(".calendar-tbl td");
1515      const tblCellBtns = datePicker.querySelectorAll(".calendar-tbl td .btn-set-date");
1516      const actionBtns = datePicker.querySelectorAll(".calendar-btn-wrap button");
1517      let clickCount = 0;
1518      let startTd = null;
1519
1520      // 캡션 설정
1521      caption.innerHTML = `${year}년 ${month}월`;
1522
1523      // 테스트용 (실제 구현에서는 날짜 배열을 받아 처리함)
1524      tblCells.forEach((cell) => {
1525        const day = cell.querySelector(".btn-set-date").innerText.padStart(2, "0");
1526        let [numberYear, numberMonth] = [parseFloat(year), parseFloat(month)];
1527        if (cell.classList.contains("old")) {
1528          if (numberMonth === 1) {
1529            numberYear -= 1;
1530            numberMonth = 12;
1531          } else {
1532            numberMonth -= 1;
1533          }
1534        } else if (cell.classList.contains("new")) {
1535          if (numberMonth === 12) {
1536            numberYear += 1;
1537            numberMonth = 1;
1538          } else {
1539            numberMonth += 1;
1540          }
1541        }
1542        cell.setAttribute("data-date", `${numberYear}.${String(numberMonth).padStart(2, "0")}.${day}`);
1543      });
1544
1545      // action
1546      const accReset = (action, btn, type) => {
1547        // 인풋 단일
1548        const targetInput = btn.closest(".calendar-conts").querySelector("input.datepicker.cal");
1549        // 인풋 분할
1550        const targetInputStart = btn.closest(".calendar-conts").querySelector(".input-group.range.set li:first-child input.datepicker");
1551        const targetInputEnd = btn.closest(".calendar-conts").querySelector(".input-group.range.set li:last-child input.datepicker");
1552
1553        action.addEventListener("click", () => {
1554          if (targetInput) {
1555            targetInput.setAttribute("type", "text");
1556            const target = action.innerText;
1557            if (target === "오늘") {
1558              accSet();
1559            }
1560            if (target === "확인") {
1561              if (type === "single") {
1562                const value = action.closest(".krds-calendar-area").querySelector("td.period.start.end").getAttribute("data-date");
1563                targetInput.value = value;
1564              } else {
1565                const value1 = action.closest(".krds-calendar-area").querySelector("td.period.start")?.getAttribute("data-date");
1566                const value2 = action.closest(".krds-calendar-area").querySelector("td.period.end")?.getAttribute("data-date") || "";
1567                targetInput.value = `${value1} ~ ${value2}`;
1568              }
1569            }
1570          } else {
1571            targetInputStart.setAttribute("type", "text");
1572            targetInputEnd.setAttribute("type", "text");
1573            const target = action.innerText;
1574            if (target === "오늘") {
1575              accSet();
1576            }
1577            if (target === "확인") {
1578              const value1 = action.closest(".krds-calendar-area").querySelector("td.period.start")?.getAttribute("data-date");
1579              const value2 = action.closest(".krds-calendar-area").querySelector("td.period.end")?.getAttribute("data-date") || "";
1580              targetInputStart.value = value1;
1581              targetInputEnd.value = value2;
1582            }
1583          }
1584        });
1585        // 공통 접근성
1586        const accSet = () => {
1587          const prevItems = action.closest(".krds-calendar-area").querySelectorAll(".period");
1588          prevItems.forEach((prev) => {
1589            prev.classList.remove("period", "start", "end");
1590            prev.querySelector(".btn-set-date").removeAttribute("aria-pressed");
1591          });
1592          action.closest(".krds-calendar-area").querySelector("td.today").classList.add("period", "start", "end");
1593          action.closest(".krds-calendar-area").querySelector("td.today .btn-set-date").setAttribute("aria-pressed", "true");
1594        };
1595      };
1596
1597      // btn-set-date
1598      tblCellBtns.forEach((btn) => {
1599        // disabled 설정
1600        if (btn.closest("td.new, td.old")) {
1601          btn.setAttribute("disabled", "true");
1602        }
1603
1604        // var
1605        const isSingle = btn.closest(".calendar-wrap").classList.contains("single");
1606
1607        if (isSingle) {
1608          btn.addEventListener("click", () => {
1609            tblCellBtns.forEach((otherBtn) => {
1610              otherBtn.closest("td").classList.remove("period", "start", "end");
1611              otherBtn.removeAttribute("aria-pressed");
1612            });
1613            btn.closest("td").classList.add("period", "start", "end");
1614            btn.setAttribute("aria-pressed", "true");  
1615          });
1616          // action
1617          actionBtns.forEach((action) => {
1618            accReset(action, btn, "single");
1619          });
1620        } else {         
1621          btn.addEventListener("click", () => {
1622            const currentTd = btn.closest("td");
1623            // 현재 td의 날짜
1624            const currentDate = new Date(currentTd.getAttribute("data-date"));
1625            // 두 번째 클릭일 때, 시작날짜 이전 이면 초기화
1626            if (startTd) {
1627              const startDate = new Date(startTd.getAttribute("data-date"));
1628              if (currentDate < startDate) {
1629                console.log("시작날짜 이전은 선택할 수 없습니다.");
1630                startTd = null;
1631                clickCount = 0;
1632                // return;
1633              }
1634            }
1635
1636            clickCount++;
1637
1638            if (clickCount % 2 === 1) {
1639              tblCellBtns.forEach((otherBtn) => {
1640                otherBtn.closest("td").classList.remove("period", "start", "end");
1641                otherBtn.removeAttribute("aria-pressed");
1642              });
1643              btn.closest("td").classList.add("period", "start");
1644              btn.setAttribute("aria-pressed", "true");
1645              startTd = currentTd;
1646            } else {
1647              btn.closest("td").classList.add("period", "end");
1648              btn.setAttribute("aria-pressed", "true");
1649              let started = false;
1650              tblCellBtns.forEach((otherBtn) => {
1651                const td = otherBtn.closest("td");
1652                if (td === startTd) {
1653                  started = true;
1654                }
1655                if (started && td !== currentTd) {
1656                  td.classList.add("period");
1657                  otherBtn.setAttribute("aria-pressed", "true");
1658                }
1659                if (td === currentTd) {
1660                  started = false;
1661                }
1662              });
1663              startTd = null;
1664            }
1665          });
1666          // action
1667          actionBtns.forEach((action) => {
1668            accReset(action, btn);
1669          });
1670        }
1671      });
1672    });
1673  },
1674};
1675
1676/*** * krds_inPageNavigation * ***/
1677const krds_inPageNavigation = {
1678  quickIndicators: null,
1679  init() {
1680    this.quickIndicators = document.querySelectorAll(".krds-in-page-navigation-type .krds-in-page-navigation-area:not(.sample) .in-page-navigation-list");
1681
1682    if (!this.quickIndicators.length) return;
1683
1684    this.observeListChanges();
1685    this.setupAnchorScroll();
1686    this.updateActiveSection();
1687  },
1688  observeListChanges() {
1689    // in-page-navigation-list 변경 시 setupAnchorScroll 호출
1690    const quickList = document.querySelector(".krds-in-page-navigation-type .in-page-navigation-list");
1691    if (!quickList) return;
1692    const observer = new MutationObserver(() => {
1693      this.setupAnchorScroll();
1694    });
1695    observer.observe(quickList, {
1696      childList: true,
1697      subtree: true,
1698    });
1699  },
1700  setupAnchorScroll() {
1701    this.quickIndicators.forEach((indicator) => {
1702      const locationList = indicator.querySelectorAll("a");
1703      locationList.forEach((anchor) => {
1704        const target = document.querySelector(anchor.getAttribute("href"));
1705        if (target) {
1706          anchor.removeEventListener("click", this.applyScroll);
1707          anchor.removeEventListener("keydown", this.applyScroll);
1708          anchor.addEventListener("click", this.applyScroll.bind(this, target));
1709          anchor.addEventListener("keydown", (event) => {
1710            if (event.key === "Enter" || event.key === " ") {
1711              this.applyScroll(target, event);
1712            }
1713          });
1714        }
1715      });
1716    });
1717  },
1718  applyScroll(target, event) {
1719    event.preventDefault();
1720    const headerHeight = this.calculateHeaderHeight();
1721
1722    window.scrollTo({
1723      left: 0,
1724      top: target.getBoundingClientRect().top + window.scrollY - headerHeight,
1725      behavior: "smooth",
1726    });
1727
1728    // enter 초점 이동
1729    if (event.type === "keydown") {
1730      const focusable = target.querySelector(".sec-tit");
1731
1732      if (focusable) {
1733        focusable.setAttribute("tabindex", "-1");
1734        focusable.focus({ preventScroll: true });
1735      }
1736    }
1737  },
1738  calculateHeaderHeight() {
1739    const headerTop = document.querySelector("#krds-masthead")?.clientHeight || 0;
1740    const headerInner = document.querySelector("#krds-header .header-in")?.clientHeight || 0;
1741    return headerTop + headerInner;
1742  },
1743  updateActiveSection() {
1744    if (!this.quickIndicators) return;
1745
1746    const winHeight = window.innerHeight;
1747    let sectionArea = [];
1748    const activeTab = document.querySelector(".tab-conts:not(.sample).active");
1749
1750    // 탭이 아닐때와 탭일때 sectionArea 설정
1751    if (activeTab) {
1752      const id = activeTab.getAttribute("id");
1753      const dataTrue = activeTab.getAttribute("data-quick-nav");
1754      if (dataTrue === "true") {
1755        sectionArea = document.querySelectorAll(`#${id} .section-link`);
1756      }
1757    } else {
1758      sectionArea = document.querySelectorAll(".scroll-check .section-link");
1759    }
1760
1761    //페이지 스크롤 시 퀵 네비게이션 해당메뉴 active
1762    if (sectionArea.length > 0) {
1763      const topHeight = Math.ceil(winHeight / 5); // 윈도우의 20%
1764      const firstSecTop = sectionArea[0].offsetTop;
1765      const scrollBottom = window.scrollY + winHeight;
1766      const scrollHeight = document.body.scrollHeight;
1767      sectionArea.forEach((current) => {
1768        const sectionHeight = current.offsetHeight;
1769        const sectionTop = current.offsetTop - topHeight;
1770        const sectionId = current.getAttribute("id");
1771        const navLink = document.querySelector(`.krds-in-page-navigation-area a[href*=${sectionId}]`);
1772        const firstAnchor = document.querySelector(".krds-in-page-navigation-area .in-page-navigation-list li:first-of-type a");
1773        const lastAnchor = document.querySelector(".krds-in-page-navigation-area .in-page-navigation-list li:last-of-type a");
1774        if (scrollBottom >= scrollHeight) {
1775          // 스크롤이 페이지 끝에 도달했을 때
1776          this.setActiveIndicator(lastAnchor);
1777        }
1778        else if (window.scrollY <= firstSecTop) {
1779          // 스크롤이 첫번째 섹션보다 위에 있을때
1780          this.setActiveIndicator(firstAnchor);
1781        } else if (window.scrollY > sectionTop && window.scrollY <= sectionTop + sectionHeight) {
1782          // 현재 섹션에 있을 때
1783          this.setActiveIndicator(navLink);
1784        }
1785      });
1786    }
1787  },
1788  setActiveIndicator(anchor) {
1789    if (anchor) {
1790      this.quickIndicators.forEach((indicator) => {
1791        const locationList = indicator.querySelectorAll("a");
1792        locationList.forEach((anchor) => {
1793          anchor.classList.remove("active");
1794        });
1795      });
1796      anchor.classList.add("active");
1797    }
1798  },
1799};
1800
1801/*** * krds_helpPanel * ***/
1802const krds_helpPanel = {
1803  helpPanel: null,
1804  lastFocusedButton: null,
1805  executeButton: null,
1806  collapseButton: null,
1807  init() {
1808    this.helpPanel = document.querySelector(".krds-help-panel");
1809
1810    if (!this.helpPanel) return;
1811
1812    this.executeButton = document.querySelectorAll(".btn-help-exec");
1813    this.collapseButton = this.helpPanel.querySelector(".btn-help-panel.fold");
1814
1815    this.setupPadding();
1816    this.observeMastHead();
1817    this.setupHelpButtons();
1818    this.toggleScrollLock();
1819  },
1820  setupPadding() {
1821    const topBannerHeight = document.querySelector("#krds-masthead")?.offsetHeight;
1822    const headerHeight = document.querySelector("#krds-header .header-in")?.offsetHeight;
1823    const defaultPadding = topBannerHeight + headerHeight;
1824    const hiddenBannerPadding = headerHeight;
1825
1826    const expandBox = document.querySelector(".help-panel-wrap");
1827    const expandButton = document.querySelector(".btn-help-panel.expand");
1828
1829    const applyPadding = (padding) => {
1830      expandButton.style.marginTop = padding;
1831      if (windowSize.getWinSize() === "pc") {
1832        expandBox.style.paddingTop = padding;
1833        this.collapseButton.style.marginTop = padding;
1834      } else {
1835        expandBox.removeAttribute("style");
1836        this.collapseButton.removeAttribute("style");
1837      }
1838    };
1839
1840    // bn-hidden: 헤더 배너 숨김, scroll-down: 헤더 숨김
1841    if (document.body.classList.contains("bn-hidden")) {
1842      if (document.querySelector("#wrap").classList.contains("scroll-down")) {
1843        applyPadding("0");
1844      } else {
1845        applyPadding(`${hiddenBannerPadding}px`);
1846      }
1847    } else {
1848      applyPadding(`${defaultPadding}px`);
1849    }
1850  },
1851  observeMastHead() {
1852    const topBanner = document.querySelector("#krds-masthead");
1853    if (!topBanner) return;
1854    const body = document.body;
1855    const observer = new IntersectionObserver(
1856      (entries) => {
1857        entries.forEach((entry) => {
1858          body.classList.toggle("bn-hidden", !entry.isIntersecting);
1859        });
1860      },
1861      { root: null, threshold: 0 }
1862    );
1863    observer.observe(topBanner);
1864  },
1865  setupHelpButtons() {
1866    this.executeButton.forEach((btn) => {
1867      btn.addEventListener("click", () => {
1868        this.lastFocusedButton = btn;
1869        this.toggleHelpPanel("open", btn);
1870      });
1871      btn.setAttribute("aria-expanded", "false");
1872    });
1873    if (this.collapseButton) {
1874      this.collapseButton.addEventListener("click", () => {
1875        this.toggleHelpPanel("close");
1876      });
1877    }
1878  },
1879  toggleHelpPanel(action, triggerBtn) {
1880    const helpWrap = document.querySelector(".help-panel-wrap");
1881    const innerContainer = document.querySelector("#container > .inner");
1882
1883    if (!helpWrap || !innerContainer || !this.helpPanel) return;
1884
1885    if (action === "open") {
1886      if (windowSize.getWinSize() === "mob") {
1887        document.body.classList.add("scroll-no");
1888      }
1889
1890      this.helpPanel.classList.add("expand");
1891      helpWrap.setAttribute("tabindex", "0");
1892
1893      if (triggerBtn) {
1894        setTimeout(() => {
1895          helpWrap.focus();
1896        }, 50);
1897      }
1898
1899      // 도움패널은 페이지에 한개만 있고 컨트롤하는 버튼은 여러개일때
1900      this.executeButton.forEach((btn) => {
1901        btn.setAttribute("aria-expanded", "true");
1902      });
1903
1904      // inner가 flexible인 경우
1905      if (innerContainer.classList.contains("help-panel-flexible")) {
1906        innerContainer.classList.add("help-panel-expanded");
1907      }
1908    } else if (action === "close") {
1909      if (windowSize.getWinSize() === "mob") {
1910        document.body.classList.remove("scroll-no");
1911      }
1912
1913      this.helpPanel.classList.remove("expand");
1914      helpWrap.removeAttribute("tabindex");
1915
1916      if (this.lastFocusedButton) {
1917        this.lastFocusedButton.focus();
1918      } else {
1919        // 처음 부터 오픈일때 지정 버튼으로 포커스 이동
1920        document.querySelector(".btn-help-panel.expand").focus();
1921      }
1922
1923      // 도움패널은 페이지에 한개만 있고 컨트롤하는 버튼은 여러개일때
1924      this.executeButton.forEach((btn) => {
1925        btn.setAttribute("aria-expanded", "false");
1926      });
1927
1928      // inner가 flexible인 경우
1929      if (innerContainer.classList.contains("help-panel-flexible")) {
1930        innerContainer.classList.remove("help-panel-expanded");
1931      }
1932    }
1933  },
1934  toggleScrollLock() {
1935    setTimeout(() => {
1936      if (windowSize.getWinSize() === "mob" && this.helpPanel.classList.contains("expand")) {
1937        document.body.classList.add("scroll-no");
1938      } else {
1939        document.body.classList.remove("scroll-no");
1940      }
1941    }, 0);
1942  },
1943};
1944
1945/*** * krds_disclosure * ***/
1946const krds_disclosure = {
1947  disclosures: null,
1948  init() {
1949    this.disclosures = document.querySelectorAll(".krds-disclosure");
1950
1951    if (!this.disclosures.length) return;
1952
1953    this.setupDisclosure();
1954  },
1955  setupDisclosure() {
1956    this.disclosures.forEach((disclosure) => {
1957      const disclosureButton = disclosure.querySelector(".btn-conts-expand");
1958      const disclosureContent = disclosure.querySelector(".expand-wrap");
1959      const uniqueIdx = `disclosure-${Math.random().toString(36).substring(2, 9)}`;
1960
1961      // 예외 처리: disclosureButton 없이 active 상태를 직접 설정하여 확장 상태를 제어하는 경우
1962      if (!disclosureButton) return;
1963
1964      // aria 속성 설정
1965      disclosureButton.setAttribute("aria-expanded", "false");
1966      disclosureButton.setAttribute("aria-controls", uniqueIdx);
1967      disclosureContent.setAttribute("id", uniqueIdx);
1968      // disclosureContent.setAttribute("aria-hidden", "true"); // 임시: disclosure 내용이 일부만 노출되는 경우
1969      disclosureContent.setAttribute("inert", "");
1970      if (disclosure.classList.contains("active")) {
1971        disclosureButton.setAttribute("aria-expanded", "true");
1972        // disclosureContent.setAttribute("aria-hidden", "false"); // 임시: disclosure 내용이 일부만 노출되는 경우
1973        disclosureContent.removeAttribute("inert");
1974      }
1975
1976      disclosureButton.addEventListener("click", () => {
1977        this.toggleDisclosure(disclosure, disclosureButton, disclosureContent);
1978      });
1979    });
1980  },
1981  toggleDisclosure(disclosure, disclosureButton, disclosureContent) {
1982    const isExpanded = disclosureButton.getAttribute("aria-expanded") === "true";
1983
1984    disclosure.classList.toggle("active", !isExpanded);
1985    disclosureButton.setAttribute("aria-expanded", !isExpanded);
1986
1987    // disclosureContent.setAttribute("aria-hidden", isExpanded); // 임시: disclosure 내용이 일부만 노출되는 경우
1988    if (isExpanded) {
1989      disclosureContent.setAttribute("inert", "");
1990    } else {
1991      disclosureContent.removeAttribute("inert");
1992    }
1993  },
1994};
1995
1996/*** * krds_adjustContentScale * ***/
1997const krds_adjustContentScale = {
1998  body: document.body,
1999  scaleLevel: 1,
2000  minScale: 0.5,
2001  maxScale: 2,
2002  init() {
2003    const scaleButtons = document.querySelectorAll("[data-adjust] [data-adjust-scale]");
2004
2005    if (!scaleButtons.length) return;
2006
2007    // root 변수에서 스케일 값을 가져오기
2008    
2009    const zoomSmall = 0.9;
2010    const zoomMedium = 1;
2011    const zoomLarge = 1.1;
2012    const zoomXlarge = 1.2;
2013    const zoomXxlarge = 1.3;
2014    
2015    //const root = document.querySelector(":root");
2016    //const rootStyles = getComputedStyle(root);
2017    //const zoomSmall = rootStyles.getPropertyValue("--krds-zoom-small").trim();
2018    //const zoomMedium = rootStyles.getPropertyValue("--krds-zoom-medium").trim();
2019    //const zoomLarge = rootStyles.getPropertyValue("--krds-zoom-large").trim();
2020    //const zoomXlarge = rootStyles.getPropertyValue("--krds-zoom-xlarge").trim();
2021    //const zoomXxlarge = rootStyles.getPropertyValue("--krds-zoom-xxlarge").trim();
2022    
2023
2024    scaleButtons.forEach((button) => {
2025      button.addEventListener("click", () => {
2026        const scale = button.getAttribute("data-adjust-scale");
2027        switch (scale) {
2028          case "sm":
2029            this.scaleValue(zoomSmall);
2030            break;
2031          case "md":
2032            const others = button.closest(".drop-menu").querySelectorAll(".item-link");
2033            const defaultSize = button.closest(".drop-menu").querySelector(".item-link.md");
2034            others.forEach((item) => {
2035              item.classList.remove("active");
2036              item.setAttribute("aria-selected", "false");
2037              item.querySelector(".sr-only").innerHTML = "";
2038            });
2039            defaultSize.classList.add("active");
2040            defaultSize.querySelector(".sr-only").innerHTML = "선택됨";
2041            this.scaleValue(zoomMedium);
2042            break;
2043          case "lg":
2044            this.scaleValue(zoomLarge);
2045            break;
2046          case "xlg":
2047            this.scaleValue(zoomXlarge);
2048            break;
2049          case "xxlg":
2050            this.scaleValue(zoomXxlarge);
2051            break;
2052          default:
2053            break;
2054        }
2055      });
2056    });
2057  },
2058  scaleValue(value) {
2059    this.scaleLevel = value;
2060    document.body.style.zoom = this.scaleLevel;
2061  },
2062  scaleDefault() {
2063    this.scaleLevel = 1;
2064    document.body.style.zoom = this.scaleLevel;
2065  },
2066/* scaleMin() {
2067    this.scaleLevel = this.minScale;
2068    this.body.style.zoom = this.scaleLevel;
2069  },
2070  scaleMax() {
2071    this.scaleLevel = this.maxScale;
2072    this.body.style.zoom = this.scaleLevel;
2073  },
2074  scaleUp() {
2075    if (this.scaleLevel < this.maxScale) {
2076      this.scaleLevel += 0.1;
2077      if (this.scaleLevel > this.maxScale) this.scaleLevel = this.maxScale;
2078      this.body.style.zoom = this.scaleLevel;
2079    }
2080  },
2081  scaleDown() {
2082    if (this.scaleLevel > this.minScale) {
2083      this.scaleLevel -= 0.1;
2084      if (this.scaleLevel < this.minScale) this.scaleLevel = this.minScale;
2085      this.body.style.zoom = this.scaleLevel;
2086    }
2087  }, */
2088};
2089
2090/*** * krds_toggleSwitch * ***/
2091const krds_toggleSwitch = {
2092  init() {
2093    const toggleSwitch = document.querySelectorAll(".krds-form-toggle-switch");
2094
2095    if (!toggleSwitch.length) return;
2096
2097    toggleSwitch.forEach((toggle) => {
2098      const input = toggle.querySelector("input");
2099      if (!input) return;
2100      input.addEventListener("focus", () => {
2101        toggle.classList.add("focus");
2102      });
2103      input.addEventListener("focusout", () => {
2104        toggle.classList.remove("focus");
2105      });
2106    });
2107  },
2108};
2109
2110/*** * krds_infoList * ***/
2111const krds_infoList = {
2112  init() {
2113    const infoLists = document.querySelectorAll(".krds-info-list");
2114
2115    if (!infoLists.length) return;
2116
2117    infoLists.forEach((list) => {
2118      list.setAttribute("role", "list");
2119      const listItems = list.querySelectorAll("li");
2120      listItems.forEach((item) => {
2121        item.setAttribute("role", "listitem");
2122      });
2123    });
2124  },
2125};
2126
2127/*** * krds_dropEvent(gnb utils / page-title-wrap) * ***/
2128const krds_dropEvent = {
2129  dropButtons: null,
2130  init() {
2131    this.dropButtons = document.querySelectorAll(".krds-drop-wrap:not(.sample) .drop-btn");
2132
2133    if (!this.dropButtons.length) return;
2134
2135    this.setupEventListeners();
2136    this.setupFocusOutEvent();
2137  },
2138  setupEventListeners() {
2139    this.dropButtons.forEach((button) => {
2140      const menu = button.nextElementSibling;
2141      button.setAttribute("aria-expanded", "false");
2142
2143      button.addEventListener("click", () => {
2144        const isOpen = menu.style.display === "block";
2145        this.closeAllDropdowns();
2146        if (!isOpen) {
2147          this.openDropdown(button, menu);
2148        }
2149      });
2150
2151      this.setupMenuItems(menu);
2152    });
2153  },
2154  setupMenuItems(menu) {
2155	if (!menu) return; // ← null 체크 추가
2156    const items = menu.querySelectorAll(".item-link");
2157
2158    items.forEach((item) => {
2159      item.innerHTML += `<span class="sr-only"></span>`;
2160      if (item.classList.contains("active")) {
2161        item.querySelector(".sr-only").innerHTML = "선택됨";
2162      }
2163
2164      item.addEventListener("click", () => {
2165        this.activateMenuItem(item);
2166        this.closeAllDropdowns();
2167        const button = item.closest(".krds-drop-wrap").querySelector(".drop-btn");
2168        button?.focus();
2169      });
2170
2171      item.addEventListener("focus", () => {
2172        document.querySelectorAll(".krds-drop-wrap .drop-list .item-link").forEach((item) => {
2173          item.style.position = "relative";
2174          item.style.zIndex = "0";
2175        });
2176        item.style.zIndex = "1";
2177      });
2178    });
2179  },
2180  activateMenuItem(selectedItem) {
2181    const menu = selectedItem.closest(".drop-menu");
2182    const items = menu.querySelectorAll(".item-link");
2183
2184    items.forEach((item) => {
2185      item.classList.remove("active");
2186      item.setAttribute("aria-selected", "false");
2187      item.querySelector(".sr-only").innerText = "";
2188    });
2189
2190    selectedItem.classList.add("active");
2191    selectedItem.setAttribute("aria-selected", "true");
2192    selectedItem.querySelector(".sr-only").innerText = "선택됨";
2193  },
2194  openDropdown(button, menu) {
2195    menu.style.display = "block";
2196    button.classList.add("active");
2197    button.setAttribute("aria-expanded", "true");
2198
2199    // 여백에 따라 위치 조정
2200    const menuRect = menu.getBoundingClientRect();
2201    const windowWidth = window.innerWidth;
2202    if (menuRect.left < 0) {
2203      menu.closest(".krds-drop-wrap").classList.add("drop-left");
2204    } else if (windowWidth < menuRect.left + menuRect.width) {
2205      menu.closest(".krds-drop-wrap").classList.add("drop-right");
2206    }
2207
2208  },
2209  closeAllDropdowns() {
2210    document.querySelectorAll(".krds-drop-wrap .drop-menu").forEach((menu) => {
2211      menu.style.display = "none";
2212    });
2213    this.dropButtons.forEach((button) => {
2214      button.classList.remove("active");
2215      button.setAttribute("aria-expanded", "false");
2216    });
2217  },
2218  setupFocusOutEvent() {
2219    // ESC 닫기
2220    document.addEventListener("keydown", (event) => {
2221      if (event.key === "Escape" || event.key === "Esc") {
2222        this.closeAllDropdowns();
2223        event.target.closest(".krds-drop-wrap")?.querySelector(".drop-btn")?.focus();
2224      }
2225    });
2226
2227    // 드롭다운 외부 클릭 시 닫기
2228    document.addEventListener("click", (event) => {
2229      if (!event.target.closest(".krds-drop-wrap")) {
2230        this.closeAllDropdowns();
2231      }
2232    });
2233
2234    // 드롭다운 포커스 아웃 처리
2235    this.dropButtons.forEach((button) => {
2236      const menu = button.nextElementSibling;
2237
2238      if (!menu) return;
2239
2240      // 드롭다운 메뉴의 포커스 아웃 처리
2241      menu.addEventListener("focusout", (event) => {
2242        const isFocusInside = menu.contains(event.relatedTarget) || button.contains(event.relatedTarget);
2243        if (!isFocusInside) {
2244          this.closeAllDropdowns();
2245        }
2246      });
2247
2248      // 버튼의 포커스 아웃 처리
2249      button.addEventListener("focusout", (event) => {
2250        const isFocusInside = menu.contains(event.relatedTarget) || button.contains(event.relatedTarget);
2251        if (!isFocusInside) {
2252          this.closeAllDropdowns();
2253        }
2254      });
2255    });
2256  },
2257};
2258
2259/*** * krds_chkBox 박스형 체크박스 상태에 따른 디자인 변경 * ***/
2260const krds_chkBox = {
2261  init() {
2262    const box = document.querySelectorAll(".chk-group-wrap");
2263    box.forEach((e) => {
2264      const boxList = e.querySelectorAll("li");
2265      boxList.forEach((ele) => {
2266        ele.removeAttribute("class");
2267        const thisList = ele.closest("li");
2268        const checkbox = ele.querySelector("input[type=checkbox]");
2269        if (checkbox != null) {
2270          const is_disabled = checkbox.disabled;
2271          let is_checked = checkbox.checked;
2272
2273          if (is_disabled == true) {
2274            thisList.classList.add("disabled");
2275          } else {
2276            if (is_checked == true) {
2277              thisList.classList.add("checked");
2278            }
2279          }
2280
2281          checkbox.addEventListener("click", () => {
2282            if (is_checked == true) {
2283              thisList.classList.remove("checked");
2284              is_checked = false;
2285            } else {
2286              thisList.classList.add("checked");
2287              is_checked = true;
2288            }
2289          });
2290        }
2291
2292        const rdo = ele.querySelector("input[type=radio]");
2293        if (rdo != null) {
2294          const is_disabled = rdo.disabled;
2295          let is_checked = rdo.checked;
2296
2297          if (is_disabled == true) {
2298            thisList.classList.add("disabled");
2299          } else {
2300            if (is_checked == true) {
2301              thisList.classList.add("checked");
2302            }
2303          }
2304
2305          rdo.addEventListener("click", (e) => {
2306            const rdoGroup = rdo.closest(".chk-group-wrap");
2307            const rdoLi = rdoGroup.querySelectorAll("li");
2308            let is_checked2 = e.checked;
2309            rdoLi.forEach((ele) => {
2310              ele.classList.remove("checked");
2311              is_checked2 = false;
2312            });
2313            if (is_checked2 == true) {
2314              thisList.classList.remove("checked");
2315              is_checked2 = false;
2316            } else {
2317              thisList.classList.add("checked");
2318              is_checked2 = true;
2319            }
2320          });
2321        }
2322      });
2323    });
2324
2325    this.formChipFocus();
2326  },
2327  formChipFocus() {
2328    // form_chip 일때 포커스 처리
2329    const formChip = document.querySelectorAll(".krds-form-chip");
2330
2331    if (!formChip.length) return;
2332    
2333    formChip.forEach((chip) => {
2334      const input = chip.querySelector("input");
2335      if (!input) return;
2336      input.addEventListener("focus", () => {
2337        chip.classList.add("focus");
2338      });
2339      input.addEventListener("focusout", () => {
2340        chip.classList.remove("focus");
2341      });
2342    });
2343
2344  },
2345};
2346
2347/*** * krds_fileUpload 파일 업로드 : drag 임시 * ***/
2348const krds_fileUpload = {
2349  init() {
2350    const fileUploads = document.querySelectorAll(".file-upload");
2351    fileUploads.forEach((fileUpload) => {
2352      const inputFile = fileUpload.querySelector('input[type="file"]');
2353      const inputButton = fileUpload.querySelector("button");
2354
2355      inputButton.addEventListener("click", () => {
2356        inputFile.click();
2357      });
2358
2359      fileUpload.addEventListener("dragover", (e) => {
2360        fileUpload.classList.add("active");
2361        e.preventDefault();
2362      });
2363
2364      fileUpload.addEventListener("dragleave", (e) => {
2365        fileUpload.classList.remove("active");
2366        e.preventDefault();
2367      });
2368
2369      fileUpload.addEventListener("drop", (e) => {
2370        fileUpload.classList.remove("active");
2371        e.preventDefault();
2372        // const files = e.dataTransfer.files;
2373      });
2374    });
2375  },
2376};
2377
2378/*** * nuriToggleEvent 누리집 토글 이벤트 현재는 사용 안 함 * ***/
2379const nuriToggleEvent = {
2380  init() {
2381    const _toggleBtns = document.querySelectorAll("#krds-masthead .toggle-btn");
2382    _toggleBtns.forEach(($toggleBtn) => {
2383      $toggleBtn.addEventListener("click", ($btnAct) => {
2384        const $target = $btnAct.target.closest(".toggle-head");
2385        const $targetBody = $target.nextElementSibling;
2386        const _targetBodyH = $targetBody.querySelector(".inner").scrollHeight;
2387        const $srEl = $btnAct.target.querySelector(".sr-only");
2388
2389        if (!$target.classList.contains("active")) {
2390          $srEl.innerText = "닫힘";
2391          $target.classList.add("active");
2392          $targetBody.classList.add("active");
2393          $targetBody.style.height = `${_targetBodyH}px`;
2394          window.addEventListener("resize", () => {
2395            if ($targetBody.classList.contains("active")) {
2396              const _targetBodyH = $targetBody.querySelector(".inner").scrollHeight;
2397              $targetBody.style.height = `${_targetBodyH}px`;
2398            }
2399          });
2400        } else {
2401          $srEl.innerText = "열림";
2402          $target.classList.remove("active");
2403          $targetBody.classList.remove("active");
2404          $targetBody.style.height = "";
2405        }
2406      });
2407    });
2408  },
2409};
2410
2411/*** * krds_tts * ***/
2412const krds_tts = {
2413  speechSynthesis: null,
2414  activeButton: null,
2415  
2416  init() {
2417    this.speechSynthesis = window.speechSynthesis;
2418    // Chrome에서 voices 초기화 트리거(비동기 로드되더라도 speak는 지연 없이 호출해야 함)
2419    try {
2420      this.speechSynthesis && this.speechSynthesis.getVoices();
2421    } catch (e) {}
2422    
2423    const ttsButtons = document.querySelectorAll(".krds-tts:not([disabled]):not(.disabled)");
2424    
2425    ttsButtons.forEach((button) => {
2426      button.setAttribute("aria-pressed", "false");
2427      // onclick이 없으면 이벤트 리스너 추가
2428      if (!button.getAttribute("onclick")) {
2429        button.addEventListener("click", (e) => {
2430          e.preventDefault();
2431          const text = button.getAttribute("data-tts-text");
2432          if (text) {
2433            this.toggle(button, text);
2434          }
2435        });
2436      }
2437    });
2438  },
2439  
2440  toggle(button, text) {
2441    const isPlaying = button.classList.contains("is-playing");
2442    
2443    if (isPlaying) {
2444      this.stop(button);
2445    } else {
2446      this.play(button, text);
2447    }
2448  },
2449  
2450  play(button, text) {
2451    if (!text || text.trim() === '') return;
2452    if (!this.speechSynthesis) return;
2453    
2454    // 다른 버튼이 재생 중이면 멈춤
2455    if (this.activeButton && this.activeButton !== button) {
2456      this.stop(this.activeButton);
2457    }
2458    
2459    // 상태 업데이트
2460    const isPlayType = button.classList.contains("play");
2461    if (isPlayType) {
2462      this.changeToPause(button);
2463    }
2464    
2465    button.classList.add("is-playing");
2466    button.setAttribute("aria-pressed", "true");
2467    this.activeButton = button;
2468    this.updateText(button, "멈춤");
2469    
2470    // TTS 재생
2471    this.speak(text, button);
2472  },
2473  
2474  stop(button) {
2475    if (this.speechSynthesis) {
2476      this.speechSynthesis.cancel();
2477    }
2478    
2479    button.classList.remove("is-playing");
2480    button.setAttribute("aria-pressed", "false");
2481    
2482    // Play 타입이었던 경우 원래대로 복원
2483    if (button.classList.contains("pause") && button.dataset.wasPlayType === "true") {
2484      this.changeToPlay(button);
2485    }
2486    
2487    this.updateText(button, "재생");
2488    
2489    if (this.activeButton === button) {
2490      this.activeButton = null;
2491    }
2492  },
2493  
2494  speak(text, button) {
2495    if (!this.speechSynthesis) return;
2496    // Chrome: user gesture 안에서 speak가 실행되어야 해서 지연 호출(setTimeout 등) 금지
2497    if (this.speechSynthesis.paused) {
2498      this.speechSynthesis.resume();
2499    }
2500    this.startUtterance(text, button);
2501  },
2502  
2503  startUtterance(text, button) {
2504    if (!this.speechSynthesis) return;
2505    const utterance = new SpeechSynthesisUtterance(text);
2506    // Chrome에서는 lang/voice 타이밍 이슈가 있어 강제 설정은 최소화
2507    utterance.rate = 1;
2508    utterance.pitch = 1;
2509    utterance.volume = 1;
2510
2511    // 가능한 경우에만 ko 음성/언어 적용 (없으면 브라우저 기본으로 진행)
2512    try {
2513      const voices = this.speechSynthesis.getVoices?.() || [];
2514      const ko = voices.find((v) => (v.lang || "").toLowerCase().startsWith("ko"));
2515      if (ko) {
2516        utterance.lang = "ko-KR";
2517        utterance.voice = ko;
2518      }
2519    } catch (e) {}
2520
2521    utterance.onend = () => {
2522      if (this.activeButton === button) {
2523        this.stop(button);
2524      }
2525    };
2526
2527    utterance.onerror = (event) => {
2528      // canceled는 사용자/코드 cancel()로 정상 발생 가능
2529      if (event.error !== "canceled" && this.activeButton === button) {
2530        this.stop(button);
2531      }
2532    };
2533
2534    try {
2535      // Chrome 우회: 같은 클릭 제스처 안에서 cancel/resume/speak를 한 번에 수행
2536      // (비동기 지연은 user-gesture를 깨서 무음이 될 수 있어 사용하지 않음)
2537      this.speechSynthesis.cancel();
2538      if (this.speechSynthesis.paused) this.speechSynthesis.resume();
2539      this.speechSynthesis.speak(utterance);
2540      // 일부 Chrome에서 speak 직후 resume이 필요할 때가 있음
2541      if (this.speechSynthesis.paused) this.speechSynthesis.resume();
2542    } catch (error) {
2543      if (this.activeButton === button) {
2544        this.stop(button);
2545      }
2546    }
2547  },
2548  
2549  changeToPause(button) {
2550    if (!button.dataset.wasPlayType) {
2551      button.dataset.wasPlayType = "true";
2552    }
2553    button.classList.remove("play");
2554    button.classList.add("pause");
2555    const icon = button.querySelector(".krds-tts-icon .svg-icon");
2556    if (icon) {
2557      icon.classList.remove("ico-play");
2558      icon.classList.add("ico-stop");
2559    }
2560  },
2561  
2562  changeToPlay(button) {
2563    button.classList.remove("pause");
2564    button.classList.add("play");
2565    const icon = button.querySelector(".krds-tts-icon .svg-icon");
2566    if (icon) {
2567      icon.classList.remove("ico-stop");
2568      icon.classList.add("ico-play");
2569    }
2570    delete button.dataset.wasPlayType;
2571  },
2572  
2573  updateText(button, newText) {
2574    const textElement = button.querySelector(".krds-tts-text");
2575    if (!textElement) return;
2576    
2577    // 원래 텍스트 저장 (처음 한 번만)
2578    if (!textElement.dataset.originalText) {
2579      textElement.dataset.originalText = textElement.textContent.trim();
2580    }
2581    
2582    // 원래 텍스트가 "재생"인 경우만 변경
2583    const originalText = textElement.dataset.originalText;
2584    if (originalText === "재생") {
2585      textElement.textContent = newText === "멈춤" ? "멈춤" : "재생";
2586    }
2587  },
2588};
2589
2590// 전역 함수: onclick에서 사용
2591// 사용법: onclick="krds_playTts('읽을 내용', this)"
2592window.krds_playTts = function(text, buttonElement) {
2593  if (!text || text.trim() === '') return;
2594  // 최소 진단(Chrome 무음 원인 확정용): voices가 0이면 환경/로딩 문제 가능성 큼
2595  try {
2596    if (window.speechSynthesis) {
2597      console.log("[krds-tts] protocol:", location.protocol, "voices:", window.speechSynthesis.getVoices().length);
2598    }
2599  } catch (e) {}
2600  
2601  // buttonElement 찾기
2602  if (!buttonElement) {
2603    const event = window.event || (typeof event !== 'undefined' ? event : null);
2604    if (event) {
2605      buttonElement = event.currentTarget || event.target?.closest('.krds-tts');
2606    }
2607    if (!buttonElement) {
2608      buttonElement = document.activeElement;
2609    }
2610  }
2611  
2612  if (!buttonElement || !buttonElement.classList.contains("krds-tts")) return;
2613  
2614  // aria-pressed 초기값 설정
2615  if (!buttonElement.hasAttribute("aria-pressed")) {
2616    buttonElement.setAttribute("aria-pressed", "false");
2617  }
2618  
2619  // 재생/멈춤 토글
2620  const isPlaying = buttonElement.classList.contains("is-playing");
2621  if (isPlaying) {
2622    krds_tts.stop(buttonElement);
2623  } else {
2624    krds_tts.play(buttonElement, text);
2625  }
2626};
2627
2628// 초기 이벤트
2629window.addEventListener("DOMContentLoaded", () => {
2630  // 윈도우 사이즈 체크
2631  windowSize.setWinSize();
2632
2633  krds_mainMenuPC.init();
2634  krds_mainMenuMobile.init();
2635  krds_sideNavigation.init();
2636  krds_tab.init();
2637  krds_accordion.init();
2638  krds_modal.init();
2639  krds_contextualHelp.init();
2640  krds_tooltip.init();
2641  krds_disclosure.init();
2642  krds_dropEvent.init();
2643  krds_calendar.init();
2644  krds_inPageNavigation.init();
2645  krds_adjustContentScale.init();
2646  krds_toggleSwitch.init();
2647  krds_infoList.init();
2648  krds_chkBox.init();
2649  krds_fileUpload.init();
2650  krds_tts.init();
2651
2652  krds_helpPanel.init();
2653  if (windowSize.getWinSize() === "pc") {
2654    krds_helpPanel.toggleHelpPanel("open");
2655  }
2656});
2657
2658// 스크롤 이벤트
2659window.addEventListener("scroll", () => {
2660  scrollManager.updateScrollValues();
2661  scrollManager.handleScrollDirection();
2662
2663  krds_mainMenuPC.closeMainMenu();
2664  krds_inPageNavigation.updateActiveSection();
2665  krds_helpPanel.init();
2666});
2667
2668// 리사이즈 이벤트
2669window.addEventListener("resize", () => {
2670  windowSize.setWinSize();
2671  krds_helpPanel.init();
2672});

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.