본문으로 건너뛰기
Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
public/js/studio.js
공개 가능
1 (() => {
2 const tabs = Array.from(document.querySelectorAll('.studio-tabs [role="tab"]'));
3 const panels = Array.from(document.querySelectorAll('.studio-screen-panel'));
4 const workspace = document.getElementById('workspace');
5 const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
6 function selectTab(tab, focus = false) {
7 tabs.forEach(item => {
8 const selected = item === tab;
9 item.setAttribute('aria-selected', String(selected));
10 item.tabIndex = selected ? 0 : -1;
11 });
12 panels.forEach(panel => { panel.hidden = panel.id !== tab.getAttribute('aria-controls'); });
13 if (focus) tab.focus();
14 }
15 tabs.forEach((tab, index) => {
16 tab.addEventListener('click', () => selectTab(tab));
17 tab.addEventListener('keydown', event => {
18 let next;
19 if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
20 if (event.key === 'ArrowLeft') next = (index + tabs.length - 1) % tabs.length;
21 if (event.key === 'Home') next = 0;
22 if (event.key === 'End') next = tabs.length - 1;
23 if (next !== undefined) { event.preventDefault(); selectTab(tabs[next], true); }
24 });
25 });
26 document.querySelectorAll('[data-studio-select]').forEach(button => {
27 button.addEventListener('click', () => {
28 const tab = document.getElementById(`tab-${button.dataset.studioSelect}`);
29 if (!tab) return;
30 selectTab(tab, true);
31 workspace.scrollIntoView({ behavior: reducedMotion.matches ? 'instant' : 'smooth', block: 'start' });
32 });
33 });
34 const dialog = document.getElementById('studio-lightbox');
35 const image = document.getElementById('studio-lightbox-image');
36 const title = document.getElementById('studio-lightbox-title');
37 const close = document.getElementById('studio-lightbox-close');
38 if (!dialog || !image || !title || !close) return;
39 let opener;
40 document.querySelectorAll('[data-studio-image]').forEach(button => {
41 button.addEventListener('click', () => {
42 opener = button;
43 title.textContent = button.dataset.studioTitle;
44 image.alt = button.dataset.studioTitle;
45 image.src = button.dataset.studioImage;
46 dialog.showModal();
47 close.focus();
48 });
49 });
50 close.addEventListener('click', () => dialog.close());
51 dialog.addEventListener('click', event => {
52 if (event.target !== dialog) return;
53 const rect = dialog.getBoundingClientRect();
54 if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) dialog.close();
55 });
56 dialog.addEventListener('close', () => { image.removeAttribute('src'); opener?.focus(); });
57 })();
58