Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
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