feat(view): 3D area with a thin title band and collapsible info panels left and right

The title, view buttons and origin sit in one 44 px band on the model. 부재 표시 is a collapsible panel on the left and
색 기준 (③ only) a collapsible panel on the right, both over the canvas, about 170 px wide, with 44 px touch targets.
Below 600 px the band wraps and the panels fall below the model.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
ypshimandClaude Haiku 5.5 committed 2026-10-23 11:14:34 +09:00
1 parent 65e751e4bb
commit 2902404061
3 files changed
+31 -10

No files matched your search

+11 -9
View File
@@ -75,20 +75,22 @@
<div class="viewer-head">
<h2 id="viewer-title">교각 3D 뷰</h2>
<div id="views" class="views" role="group" aria-label="시점"></div>
<div class="rebar-color" id="rebar-color" hidden role="group" aria-label="철근 색 기준">
<span class="lbl">색 기준</span>
<span id="origin" class="muted"></span>
</div>
<div id="canvas" class="canvas" aria-label="교각 3D 뷰"></div>
<details class="info-panel parts-info" open>
<summary>부재 표시</summary>
<div id="parts"></div>
</details>
<details class="info-panel color-info" id="color-info" open hidden>
<summary>색 기준</summary>
<div class="rebar-color" id="rebar-color" role="group" aria-label="철근 색 기준">
<button type="button" data-color="role" aria-pressed="true">역할</button>
<button type="button" data-color="diameter" aria-pressed="false">직경</button>
<button type="button" data-color="member" aria-pressed="false">부재</button>
<ul id="rebar-legend" class="rebar-legend"></ul>
</div>
<span id="origin" class="muted"></span>
</div>
<div id="canvas" class="canvas" aria-label="교각 3D 뷰"></div>
<fieldset class="parts">
<legend>부재 표시</legend>
<div id="parts"></div>
</fieldset>
</details>
</section>
<aside class="panel results" aria-labelledby="result-title">
+1 -1
View File
@@ -811,7 +811,7 @@ const REBAR_MEMBERS: { id: RebarRow['member']; label: string }[] = [
/** 3D colour basis buttons on ③ and their legend (spec §6.7). */
function renderRebarColor(): void {
// The colour control lives in the 3D window and is shown only on ③.
$<HTMLDivElement>('rebar-color').hidden = state.stage !== 'rebar';
$<HTMLDetailsElement>('color-info').hidden = state.stage !== 'rebar';
for (const b of document.querySelectorAll<HTMLButtonElement>('#rebar-color button')) {
b.setAttribute('aria-pressed', String(b.dataset.color === state.rebarColor));
}
+19
View File
@@ -760,3 +760,22 @@ body { background: #eef1f4; font-family: 'Noto Sans KR', system-ui, sans-serif;
.viewer-slot .canvas { height: 60vh; min-height: 420px; flex: none; }
.rebar-table thead th { position: sticky; top: 0; background: #ffffff; z-index: 1; }
}
/* 3D 영역 (승인 2026-10-23): 상단 띠와 좌·우 정보창을 캔버스 위에 얇게 띄움. 조작 요소 44px. */
#viewer-block { position: relative; }
#viewer-block .viewer-head { position: absolute; top: 8px; left: 8px; right: 8px; z-index: 5; margin: 0; height: 44px; padding: 0 10px; flex-wrap: nowrap; background: rgba(255, 255, 255, 0.92); border-radius: 6px; }
#viewer-block .viewer-head h2 { font-size: 14px; white-space: nowrap; }
#viewer-block .viewer-head #origin { margin-left: auto; font-size: 12px; white-space: nowrap; }
#viewer-block .info-panel { position: absolute; z-index: 5; top: 60px; max-height: calc(100% - 120px); overflow: auto; background: rgba(255, 255, 255, 0.92); border: 1px solid #d5dde6; border-radius: 6px; box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12); padding: 0 8px 8px; font-size: 13px; }
#viewer-block .info-panel > summary { min-height: 44px; display: flex; align-items: center; cursor: pointer; font-weight: 700; color: #18212b; list-style-position: outside; }
#viewer-block .parts-info { left: 8px; width: 168px; }
#viewer-block .color-info { right: 8px; width: 180px; }
#viewer-block .parts-info #parts label { display: flex; align-items: center; gap: 8px; min-height: 44px; }
#viewer-block .color-info .rebar-color { display: flex; flex-direction: column; align-items: stretch; gap: 0; margin: 0; }
#viewer-block .color-info .rebar-color button { width: 100%; }
#viewer-block .color-info .rebar-legend { flex-direction: column; gap: 4px; padding: 6px 0 0; }
@media (max-width: 600px) {
#viewer-block .viewer-head { position: static; height: auto; flex-wrap: wrap; }
#viewer-block .info-panel { position: static; width: auto; max-height: none; margin: 8px 0 0; }
}
#viewer-block .parts-info .parts-row { display: flex; flex-direction: column; align-items: stretch; gap: 0; }