+ 색 기준
diff --git a/src/main.ts b/src/main.ts
index 383b5b3..aa7cb2a 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -244,6 +244,9 @@ function setView(kind: ViewKind): void {
document.querySelectorAll('#views button').forEach((b, i) => {
b.setAttribute('aria-pressed', String(VIEWS[i].kind === kind));
});
+ // The band shows the current view only; choosing one closes the menu (승인 2026-10-23).
+ $('views-toggle').textContent = `${VIEWS.find((v) => v.kind === kind)?.label ?? ''} ▾`;
+ setMenuOpen('views', false);
if (!state.scene) return;
const pier = activePier();
if (!pier?.model) return;
@@ -445,6 +448,7 @@ function renderDefaults(): void {
}
function renderAll(): void {
+ updatePartsCount();
$('status').textContent = state.piers.length ? '' : '엑셀을 불러오면 교각 형상을 생성합니다.';
renderHeader();
renderPierList();
@@ -810,6 +814,8 @@ const REBAR_MEMBERS: { id: RebarRow['member']; label: string }[] = [
/** 3D colour basis buttons on ③ and their legend (spec §6.7). */
function renderRebarColor(): void {
+ const colorLabel: Record = { role: '역할', diameter: '직경', member: '부재' };
+ $('color-state').textContent = `${colorLabel[state.rebarColor]} ▾`;
// The colour control lives in the 3D window and is shown only on ③.
$('color-info').hidden = state.stage !== 'rebar';
for (const b of document.querySelectorAll('#rebar-color button')) {
@@ -1769,3 +1775,52 @@ if (new URLSearchParams(window.location.search).get('sample') === '1') {
})
.catch((err: Error) => setError(`샘플을 불러오지 못했습니다: ${err.message}`));
}
+
+// --- 3D band: collapsible view menu, origin toggle, and remembered panel states (승인 2026-10-23) ---
+/** Open or close the view menu or the origin note, and keep the buttons' aria-expanded in step. */
+function setMenuOpen(which: 'views' | 'origin', open: boolean): void {
+ const panel = $(which);
+ panel.hidden = !open;
+ $(`${which}-toggle`).setAttribute('aria-expanded', String(open));
+}
+
+/** Counts the bar parts shown on the 3D view in the 부재 표시 summary. */
+function updatePartsCount(): void {
+ const boxes = document.querySelectorAll('#parts input[type="checkbox"]');
+ const on = Array.from(boxes).filter((b) => b.checked).length;
+ $('parts-count').textContent = `${on}/${boxes.length} ▾`;
+}
+
+/** Panel open states remembered per viewer; the storage can be missing or blocked, so every access is guarded. */
+const PANEL_KEY = 'pierzainer.panel.';
+function restorePanel(id: string): void {
+ const panel = $(id);
+ try {
+ panel.open = localStorage.getItem(PANEL_KEY + id) === '1';
+ } catch {
+ panel.open = false;
+ }
+ panel.addEventListener('toggle', () => {
+ try {
+ localStorage.setItem(PANEL_KEY + id, panel.open ? '1' : '0');
+ } catch {
+ /* storage unavailable: the panel still works */
+ }
+ });
+}
+restorePanel('parts-info');
+restorePanel('color-info');
+
+$('views-toggle').addEventListener('click', () => setMenuOpen('views', $('views').hidden === true));
+$('origin-toggle').addEventListener('click', () => setMenuOpen('origin', $('origin').hidden === true));
+document.addEventListener('click', (ev) => {
+ const target = ev.target as Node;
+ if (!$('views-menu').contains(target)) setMenuOpen('views', false);
+ if (!$('origin-toggle').contains(target) && !$('origin').contains(target)) setMenuOpen('origin', false);
+});
+document.addEventListener('keydown', (ev) => {
+ if (ev.key === 'Escape') {
+ setMenuOpen('views', false);
+ setMenuOpen('origin', false);
+ }
+});
diff --git a/src/style.css b/src/style.css
index 4ac4a28..1844e61 100644
--- a/src/style.css
+++ b/src/style.css
@@ -782,5 +782,15 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
/* 좁은 창 (601–1100px, 승인 2026-10-23): 상단 띠에서 시점 버튼과 원점 표기를 오른쪽으로 */
@media (min-width: 601px) and (max-width: 1100px) {
- #viewer-block .viewer-head #views { margin-left: auto; }
+ #viewer-block .viewer-head #views-menu { margin-left: auto; }
}
+
+/* 3D band (승인 2026-10-23): view menu, origin popover, collapsed panel summaries */
+#viewer-block .views-menu { position: relative; margin-left: auto; }
+#viewer-block .views-toggle { min-height: 44px; font-weight: 700; background: #eef3f8; border: 1px solid #c3cbd4; }
+#viewer-block #views.views-list { position: absolute; top: 100%; right: 0; z-index: 10; display: flex; flex-direction: column; gap: 0; min-width: 120px; padding: 4px; margin-top: 4px; background: #ffffff; border: 1px solid #c3cbd4; border-radius: 6px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.15); }
+#viewer-block #views.views-list .btn { min-height: 44px; justify-content: flex-start; text-align: left; border: 0; background: transparent; }
+#viewer-block #views.views-list .btn[aria-pressed='true'] { background: #1f5fa8; color: #ffffff; }
+#viewer-block .info-btn { min-width: 44px; min-height: 44px; border: 1px solid #c3cbd4; border-radius: 6px; background: #ffffff; font-size: 16px; cursor: pointer; }
+#viewer-block .origin-pop { position: absolute; top: 100%; right: 8px; z-index: 10; margin-top: 4px; padding: 6px 10px; background: #ffffff; border: 1px solid #c3cbd4; border-radius: 6px; font-size: 12px; white-space: nowrap; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); }
+#viewer-block .panel-state { font-weight: 400; color: #55606e; margin-left: 6px; font-size: 12px; }