refactor(B05): 날 단추 · 작은 도구 단추를 부품 단추 · 아이콘 단추 · 켜기 칩 · 목록 줄로 (PLAN 553-2)

- 종단 도구 · [창 분할] = 단추 유리 · 보조 단 18 · ▲▼ = 아이콘 단추 선 18 · 배율 + − ⤢ = 아이콘 단추 22
- 배수 머리 단추 = 단추 22 · 층 단추 = 켜기 칩 20(색 점 = 층 색) · 유역 줄 = 목록 줄 32
- [추가] · [삭제] · [리셋] · [해제] · 확폭 = 단추 주 · 위험 고스트 · 보조 · [-][+] = 아이콘 단추 24
- 지도 도구줄 · 3D 보기 줄 = 단 32(34 에서) · 접기 ▾ = 아이콘 단추 18 · 잰 값 닫기 = 아이콘 단추 24
- 페이지 CSS 의 단추 꼴(테두리 · 바탕 · 글 · 둥글기) 지움 · 날 색 대체값(#d98a00 · #5b8def · #3a3f4a) 지움
- 시험 선택자 · 가짜 DOM(setAttribute) 같이 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 22:51:58 +09:00
co-authored by Claude Opus 5.5
parent 12fda57346
commit baea638043
28 changed files with 157 additions and 282 deletions
+1 -8
View File
@@ -55,16 +55,9 @@
display: none; display: none;
} }
.b05-route-profile__tools > .b05-route-profile__popout { /* 유토곡선 · 배수유역 판 [창 분할](계획서 325-3) — 종단 [창 분할]과 같은 양식(부품 단추 단 18 · 553-2) · 유토는 판 좌상단에 띄움. */
padding: var(--btn-bar-padding);
font-size: var(--btn-bar-font-size);
}
/* 유토곡선 · 배수유역 판 [창 분할](계획서 325-3) — 종단 [창 분할]과 같은 양식 · 유토는 판 좌상단에 띄움. */
.b05-subpanel__popout { .b05-subpanel__popout {
flex: none; flex: none;
padding: var(--btn-bar-padding);
font-size: var(--btn-bar-font-size);
} }
.b05-profile__masshaul-overlay > .b05-subpanel__popout { .b05-profile__masshaul-overlay > .b05-subpanel__popout {
@@ -123,6 +123,7 @@ export function attachProfilePopout(
button: HTMLButtonElement, button: HTMLButtonElement,
): void { ): void {
button.className = "ui-btn ui-btn--ghost b05-route-profile__popout"; button.className = "ui-btn ui-btn--ghost b05-route-profile__popout";
button.setAttribute("data-size", "18"); // 부품 단추 단 18(553-2) — 옆 도구 단추와 같은 키
button.title = "종단 하단 패널을 별도 창으로 분할 · 다시 누르면 결합"; button.title = "종단 하단 패널을 별도 창으로 분할 · 다시 누르면 결합";
attachPopout(profile, { attachPopout(profile, {
piece: profile.root, piece: profile.root,
@@ -139,6 +140,7 @@ export function attachSubPanelPopouts(profile: DesignProfile): void {
const subButton = (title: string): HTMLButtonElement => { const subButton = (title: string): HTMLButtonElement => {
const button = document.createElement("button"); const button = document.createElement("button");
button.className = "ui-btn ui-btn--ghost b05-subpanel__popout"; button.className = "ui-btn ui-btn--ghost b05-subpanel__popout";
button.setAttribute("data-size", "18");
button.title = title; button.title = title;
return button; return button;
}; };
+2 -2
View File
@@ -283,7 +283,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
if (!c.required && hooks.count?.(c) === undefined) { if (!c.required && hooks.count?.(c) === undefined) {
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.type = "button"; remove.type = "button";
remove.className = "b05-route__irregular-btn"; remove.className = "ui-btn ui-btn--danger-ghost b05-route__irregular-btn";
remove.textContent = "삭제"; remove.textContent = "삭제";
remove.title = `${c.title} 상자 삭제`; remove.title = `${c.title} 상자 삭제`;
remove.dataset.specRemove = c.id; remove.dataset.specRemove = c.id;
@@ -333,7 +333,7 @@ export function createSpecForm(links: SpecLinks | null, hooks: SpecFormHooks = {
if (c.required || (c.max && list.length >= c.max) || hooks.count?.(c) !== undefined) continue; if (c.required || (c.max && list.length >= c.max) || hooks.count?.(c) !== undefined) continue;
const add = document.createElement("button"); const add = document.createElement("button");
add.type = "button"; add.type = "button";
add.className = "b05-route__irregular-btn b05-structure__spec-add"; add.className = "ui-btn ui-btn--ghost b05-route__irregular-btn b05-structure__spec-add";
add.textContent = `+ ${containerTitle(c, names, groupNames[c.link])}`; add.textContent = `+ ${containerTitle(c, names, groupNames[c.link])}`;
add.title = "추가"; add.title = "추가";
add.dataset.specAdd = c.id; add.dataset.specAdd = c.id;
@@ -72,11 +72,15 @@ export function createDrainageChrome(
const updateButton = document.createElement("button"); const updateButton = document.createElement("button");
updateButton.type = "button"; updateButton.type = "button";
updateButton.className = "b05-drainage__analyze b05-drainage__update"; // 머리 줄 단추 = 부품 「단추」 보조 · 단 22(553-2)
updateButton.className = "ui-btn ui-btn--ghost b05-drainage__analyze b05-drainage__update";
updateButton.setAttribute("data-size", "22");
updateButton.textContent = "업데이트"; updateButton.textContent = "업데이트";
const proposeButton = document.createElement("button"); const proposeButton = document.createElement("button");
proposeButton.type = "button"; proposeButton.type = "button";
proposeButton.className = "b05-drainage__analyze b05-drainage__tool b05-drainage__propose"; proposeButton.className =
"ui-btn ui-btn--ghost b05-drainage__analyze b05-drainage__tool b05-drainage__propose";
proposeButton.setAttribute("data-size", "22");
proposeButton.textContent = ui_locales.B05_Drainage_Btn_Propose[currentLanguageIndex]; proposeButton.textContent = ui_locales.B05_Drainage_Btn_Propose[currentLanguageIndex];
proposeButton.title = ui_locales.B05_Drainage_Btn_Propose_Tip[currentLanguageIndex]; proposeButton.title = ui_locales.B05_Drainage_Btn_Propose_Tip[currentLanguageIndex];
header.append(updateButton, proposeButton); header.append(updateButton, proposeButton);
@@ -121,9 +121,12 @@ export function stepper(input: HTMLInputElement, stepM: number, decimals = 1): H
const button = (label: string, delta: number): HTMLButtonElement => { const button = (label: string, delta: number): HTMLButtonElement => {
const element = document.createElement("button"); const element = document.createElement("button");
element.type = "button"; element.type = "button";
element.className = "b05-structure__step"; // 부품 「아이콘 단추」 단 24(553-2) · 폭은 덩어리 몫(28)
element.className = "ui-icon-btn ui-icon-btn--ghost b05-structure__step";
element.setAttribute("data-size", "24");
element.textContent = label; element.textContent = label;
element.title = `${stepM}m ${delta > 0 ? "늘리기" : "줄이기"}`; element.title = `${stepM}m ${delta > 0 ? "늘리기" : "줄이기"}`;
element.setAttribute("aria-label", element.title);
element.addEventListener("click", nudge(delta)); element.addEventListener("click", nudge(delta));
return element; return element;
}; };
+22 -15
View File
@@ -10,6 +10,8 @@ import { stateKey } from "../A00_Common/b_page_state";
import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale";
import { themeColor } from "@ui/ui_template_palette"; import { themeColor } from "@ui/ui_template_palette";
import { showToast } from "@ui/ui_template_elements"; import { showToast } from "@ui/ui_template_elements";
import { createToggle } from "@ui/ui_template_toggle";
import { setListRowActive } from "@ui/ui_template_list_row";
import { import {
DRAINAGE_SHEET_LAYERS, DRAINAGE_SHEET_LAYERS,
fetchCachedSheetLayer, fetchCachedSheetLayer,
@@ -92,7 +94,7 @@ export function formatArea(areaM2: number): string {
return areaM2 >= 10000 ? `${(areaM2 / 10000).toFixed(2)}ha` : `${Math.round(areaM2)}㎡`; return areaM2 >= 10000 ? `${(areaM2 / 10000).toFixed(2)}ha` : `${Math.round(areaM2)}㎡`;
} }
/** 제목 우측 표시 토글 — 등고선·세류·흐름 화살표·상류 세류가 모두 같은 양식을 쓴다. */ /** 제목 우측 표시 토글 — 등고선·세류·흐름 화살표·상류 세류가 모두 같은 양식(부품 「켜기」 칩 · 단 20 · 색 점 = 층 색 · 553-2). */
export function addLayerToggle( export function addLayerToggle(
container: HTMLElement, container: HTMLElement,
label: string, label: string,
@@ -101,20 +103,20 @@ export function addLayerToggle(
onToggle: (next: boolean) => void, onToggle: (next: boolean) => void,
title?: string, title?: string,
): HTMLButtonElement { ): HTMLButtonElement {
const button = document.createElement("button"); const button = createToggle({
button.type = "button"; kind: "chip",
button.className = "b05-drainage__layer-button" + (initial ? " is-active" : ""); label,
button.textContent = label; checked: initial,
button.style.setProperty("--b05-layer-color", color); dot: color,
button.setAttribute("aria-pressed", String(initial)); size: 20,
onChange: (active) => {
button.classList.toggle("is-active", active);
onToggle(active);
},
}).root;
button.classList.add("b05-drainage__layer-button");
button.classList.toggle("is-active", initial);
if (title) button.title = title; if (title) button.title = title;
let active = initial;
button.addEventListener("click", () => {
active = !active;
button.classList.toggle("is-active", active);
button.setAttribute("aria-pressed", String(active));
onToggle(active);
});
container.append(button); container.append(button);
return button; return button;
} }
@@ -140,7 +142,12 @@ export function renderBasinRows(
basins.forEach((basin) => { basins.forEach((basin) => {
const row = document.createElement("button"); const row = document.createElement("button");
row.type = "button"; row.type = "button";
row.className = "b05-drainage__basin" + (selected === basin.index ? " is-selected" : ""); // 목록 줄 = 부품 「목록 줄」 단 32(553-2) · 옛 이름(is-selected)은 자리 몫
const picked = selected === basin.index;
row.className =
"ui-list-row ui-sidebar-row b05-drainage__basin" + (picked ? " is-selected" : "");
row.setAttribute("data-size", "32");
setListRowActive(row, picked);
const badge = document.createElement("span"); const badge = document.createElement("span");
badge.className = "b05-drainage__basin-index"; badge.className = "b05-drainage__basin-index";
badge.textContent = String(basin.index); badge.textContent = String(basin.index);
@@ -58,12 +58,14 @@ export function createDrainagePropose(deps: {
const title = document.createElement("span"); const title = document.createElement("span");
const add = document.createElement("button"); const add = document.createElement("button");
add.type = "button"; add.type = "button";
add.className = "b05-drainage__analyze"; add.className = "ui-btn ui-btn--filled b05-drainage__analyze";
add.setAttribute("data-size", "22");
add.textContent = "넣기"; add.textContent = "넣기";
add.title = "고른 후보만 관으로 넣습니다 — 초안이라 [저장] 때 남습니다."; add.title = "고른 후보만 관으로 넣습니다 — 초안이라 [저장] 때 남습니다.";
const shut = document.createElement("button"); const shut = document.createElement("button");
shut.type = "button"; shut.type = "button";
shut.className = "b05-drainage__analyze"; shut.className = "ui-btn ui-btn--ghost b05-drainage__analyze";
shut.setAttribute("data-size", "22");
shut.textContent = "닫기"; shut.textContent = "닫기";
head.append(title, add, shut); head.append(title, add, shut);
const list = document.createElement("ul"); const list = document.createElement("ul");
@@ -51,7 +51,8 @@ export function createDrainageSuggest(
const title = document.createElement("span"); const title = document.createElement("span");
const apply = document.createElement("button"); const apply = document.createElement("button");
apply.type = "button"; apply.type = "button";
apply.className = "b05-drainage__analyze"; apply.className = "ui-btn ui-btn--filled b05-drainage__analyze";
apply.setAttribute("data-size", "22");
apply.textContent = "적용"; apply.textContent = "적용";
const applyTitle = const applyTitle =
"관경 · 시설 제안을 관 초안에 한 번에 얹습니다 — [저장] 때 남습니다(관 자리는 [자동 제안])."; "관경 · 시설 제안을 관 초안에 한 번에 얹습니다 — [저장] 때 남습니다(관 자리는 [자동 제안]).";
@@ -233,14 +233,14 @@ export function createIrregularStationsSection(
const primary = document.createElement("button"); const primary = document.createElement("button");
primary.type = "button"; primary.type = "button";
primary.className = "b05-route__irregular-btn is-primary"; primary.className = "ui-btn ui-btn--filled b05-route__irregular-btn is-primary";
const remove = document.createElement("button"); const remove = document.createElement("button");
remove.type = "button"; remove.type = "button";
remove.className = "b05-route__irregular-btn is-danger"; remove.className = "ui-btn ui-btn--danger-ghost b05-route__irregular-btn is-danger";
remove.textContent = "삭제"; remove.textContent = "삭제";
const reset = document.createElement("button"); const reset = document.createElement("button");
reset.type = "button"; reset.type = "button";
reset.className = "b05-route__irregular-btn"; reset.className = "ui-btn ui-btn--ghost b05-route__irregular-btn";
reset.textContent = "리셋"; reset.textContent = "리셋";
const actions = document.createElement("div"); const actions = document.createElement("div");
actions.className = "b05-route__irregular-actions"; actions.className = "b05-route__irregular-actions";
+4
View File
@@ -109,6 +109,10 @@ export function createViewControls(callbacks: ViewControlCallbacks): {
separator3, separator3,
corridorRefresh, corridorRefresh,
); );
// 보기 줄 단추 = 부품 단추 단 32(553-2)
viewControls
.querySelectorAll<HTMLElement>(".ui-btn")
.forEach((b) => b.setAttribute("data-size", "32"));
return { return {
viewControls, viewControls,
+5 -2
View File
@@ -23,7 +23,7 @@ import { viewOf } from "../common_util/common_util_view_window";
/** 버튼 폭(21px)에 여유를 더한 값 — 이보다 가까우면 겹친 것으로 본다. */ /** 버튼 폭(21px)에 여유를 더한 값 — 이보다 가까우면 겹친 것으로 본다. */
const BUTTON_CLEARANCE_PX = 23; const BUTTON_CLEARANCE_PX = 23;
const BUTTON_HALF_PX = 10; const BUTTON_HALF_PX = 9; // 부품 아이콘 단추 단 18 의 반(553-2)
/** 길게 누르기: 이만큼 유지하면 반복이 시작되고, 그 뒤 초당 10회(0.1m씩)로 이어진다. */ /** 길게 누르기: 이만큼 유지하면 반복이 시작되고, 그 뒤 초당 10회(0.1m씩)로 이어진다. */
export const HOLD_DELAY_MS = 500; export const HOLD_DELAY_MS = 500;
export const HOLD_INTERVAL_MS = 100; export const HOLD_INTERVAL_MS = 100;
@@ -286,8 +286,11 @@ function overlayButton(
): HTMLButtonElement { ): HTMLButtonElement {
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = `b05-profile-edit__btn ${className}`; // 부품 「아이콘 단추」 선 꼴 · 단 18(553-2) · 글자 기호 그대로
button.className = `ui-icon-btn ui-icon-btn--outline b05-profile-edit__btn ${className}`;
button.setAttribute("data-size", "18");
button.textContent = glyph; button.textContent = glyph;
button.setAttribute("aria-label", title);
button.title = `${title}\n(길게 누르면 0.5초 뒤부터 연속 조정)`; button.title = `${title}\n(길게 누르면 0.5초 뒤부터 연속 조정)`;
// 포인터로 누르면 즉시 1회 반응하고, 이어지는 click은 중복이므로 삼킨다. // 포인터로 누르면 즉시 1회 반응하고, 이어지는 click은 중복이므로 삼킨다.
let swallowClick = false; let swallowClick = false;
+3 -1
View File
@@ -73,10 +73,12 @@ export interface ProfileTools {
mountOverlay: (geom: FreeChartGeom) => void; mountOverlay: (geom: FreeChartGeom) => void;
} }
/** 도구 단추 = 부품 「단추」 유리 · 단 18(553-2 · 클래스로 — 시험이 별칭 없이 이 파일을 돌림) */
function toolButton(label: string, title: string, onClick: () => void): HTMLButtonElement { function toolButton(label: string, title: string, onClick: () => void): HTMLButtonElement {
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = "b05-route-profile__tool"; button.className = "ui-btn ui-btn--glass b05-route-profile__tool";
button.setAttribute("data-size", "18");
button.textContent = label; button.textContent = label;
button.title = title; button.title = title;
button.addEventListener("click", (event) => { button.addEventListener("click", (event) => {
+4 -1
View File
@@ -45,9 +45,12 @@ export function createProfileZoom(onChange: () => void): ProfileZoom {
function add(label: string, title: string, action: () => void): HTMLButtonElement { function add(label: string, title: string, action: () => void): HTMLButtonElement {
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = "b05-profile__zoom-btn"; // 부품 「아이콘 단추」 단 22(553-2) · 글자 기호 그대로
button.className = "ui-icon-btn ui-icon-btn--ghost b05-profile__zoom-btn";
button.setAttribute("data-size", "22");
button.textContent = label; button.textContent = label;
button.title = title; button.title = title;
button.setAttribute("aria-label", title);
button.addEventListener("click", (event) => { button.addEventListener("click", (event) => {
// 카드·측점 선택으로 번지면 그래프를 다시 그리며 방금 맞춘 배율이 날아간다. // 카드·측점 선택으로 번지면 그래프를 다시 그리며 방금 맞춘 배율이 날아간다.
event.stopPropagation(); event.stopPropagation();
+19 -18
View File
@@ -1,4 +1,4 @@
import { createButton } from "@ui/ui_template_elements"; import { createIconButton } from "@ui/ui_template_elements";
import { createSidePanels } from "@ui/ui_template_side_panels"; import { createSidePanels } from "@ui/ui_template_side_panels";
import { bindFootprintToggle } from "./B05_Profile_UI_RouteEdit_Footprint"; import { bindFootprintToggle } from "./B05_Profile_UI_RouteEdit_Footprint";
import { bindMarkToggle } from "./B05_Profile_UI_RouteEdit_Marks"; import { bindMarkToggle } from "./B05_Profile_UI_RouteEdit_Marks";
@@ -80,38 +80,38 @@ export function createRouteEditChrome(
<div class="b05-routeedit__canvas-wrap" aria-label="계획노선 편집"> <div class="b05-routeedit__canvas-wrap" aria-label="계획노선 편집">
<canvas class="b05-routeedit__canvas"></canvas> <canvas class="b05-routeedit__canvas"></canvas>
<div class="b05-route__view-controls b05-routeedit__tools"> <div class="b05-route__view-controls b05-routeedit__tools">
<button type="button" class="ui-btn ui-btn--glass b05-routeedit__hint-toggle" <button type="button" data-size="32" class="ui-btn ui-btn--glass b05-routeedit__hint-toggle"
data-act="hint-toggle" aria-expanded="false" title="조작 도움말 펼치기 · 접기">?</button> data-act="hint-toggle" aria-expanded="false" title="조작 도움말 펼치기 · 접기">?</button>
<i class="b05-route__view-separator" aria-hidden="true"></i> <i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" class="ui-btn ui-btn--glass" data-act="undo" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="undo"
title="되돌리기 (Ctrl+Z)" aria-label="되돌리기" disabled>${HISTORY_ICON.undo}</button> title="되돌리기 (Ctrl+Z)" aria-label="되돌리기" disabled>${HISTORY_ICON.undo}</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="redo" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="redo"
title="다시하기 (Ctrl+Y)" aria-label="다시하기" disabled>${HISTORY_ICON.redo}</button> title="다시하기 (Ctrl+Y)" aria-label="다시하기" disabled>${HISTORY_ICON.redo}</button>
<i class="b05-route__view-separator" aria-hidden="true"></i> <i class="b05-route__view-separator" aria-hidden="true"></i>
<span data-slot="tools"></span> <span data-slot="tools"></span>
<i class="b05-route__view-separator" aria-hidden="true"></i> <i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-ccw" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-ccw"
title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button> title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-cw" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-cw"
title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button> title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="contour-minor" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="contour-minor"
aria-pressed="false" disabled aria-pressed="false" disabled
title="1m 간격 등고선을 얇고 옅게 겹칩니다 (확대하면 보임)">1m 등고선</button> title="1m 간격 등고선을 얇고 옅게 겹칩니다 (확대하면 보임)">1m 등고선</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="stations" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="stations"
title="측점 눈금 · 번호를 평면에 보이기 · 숨기기 (시점 · 종점 이름표는 늘 보임)">측점</button> title="측점 눈금 · 번호를 평면에 보이기 · 숨기기 (시점 · 종점 이름표는 늘 보임)">측점</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="slopes" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="slopes"
title="측점 사이 기울기(%)를 평면에 보이기 · 숨기기 (확대해야 보임)">기울기</button> title="측점 사이 기울기(%)를 평면에 보이기 · 숨기기 (확대해야 보임)">기울기</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="footprint" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="footprint"
title="도로 끝 · 비탈 끝(깎기 붉음 · 쌓기 청록)을 평면에 겹칩니다">절/성토선</button> title="도로 끝 · 비탈 끝(깎기 붉음 · 쌓기 청록)을 평면에 겹칩니다">절/성토선</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="measure" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="measure"
title="노선 위 두 점을 눌러 거리·기울기를 잽니다 (Shift+클릭도 같음)">거리 재기</button> title="노선 위 두 점을 눌러 거리·기울기를 잽니다 (Shift+클릭도 같음)">거리 재기</button>
<i class="b05-route__view-separator" aria-hidden="true"></i> <i class="b05-route__view-separator" aria-hidden="true"></i>
<button type="button" class="ui-btn ui-btn--glass" data-act="station-snap" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="station-snap"
aria-pressed="false" aria-pressed="false"
title="켜면 고친 곡선의 가운데(MC)를 고른 간격 배수에 맞춥니다 — 일반측점(20 m)은 그대로 · MC 가 일반측점 위가 아니면 「No.N+10 (MC)」 특별측점을 더함 · 손대지 않은 곡선은 그 자리 유지 · 고정 교각점은 그대로 · 못 맞춘 곡선은 상태줄 「측점 밖 곡선」 · [저장] 때 저장">측점 스냅</button> title="켜면 고친 곡선의 가운데(MC)를 고른 간격 배수에 맞춥니다 — 일반측점(20 m)은 그대로 · MC 가 일반측점 위가 아니면 「No.N+10 (MC)」 특별측점을 더함 · 손대지 않은 곡선은 그 자리 유지 · 고정 교각점은 그대로 · 못 맞춘 곡선은 상태줄 「측점 밖 곡선」 · [저장] 때 저장">측점 스냅</button>
<label class="b05-routeedit__terrain b05-routeedit__snap-step" <label class="b05-routeedit__terrain b05-routeedit__snap-step"
title="측점 스냅 간격 — 곡선 가운데(MC)를 이 간격 배수에 맞춥니다(측점 No. = 측점 간격)">간격 title="측점 스냅 간격 — 곡선 가운데(MC)를 이 간격 배수에 맞춥니다(측점 No. = 측점 간격)">간격
<select class="ui-select ui-select--sm" data-act="snap-step"> <select class="ui-select ui-select--sm" data-size="32" data-act="snap-step">
<option value="10">10 m</option> <option value="10">10 m</option>
<option value="station">측점 No.</option> <option value="station">측점 No.</option>
</select> </select>
@@ -140,7 +140,7 @@ export function createRouteEditChrome(
<span><i class="is-planned"></i> 계획노선</span> <span><i class="is-planned"></i> 계획노선</span>
</span> </span>
<span class="b05-routeedit__summary"></span> <span class="b05-routeedit__summary"></span>
<button type="button" class="b05-routeedit__info-toggle" data-act="info-toggle" <button type="button" data-size="18" class="ui-icon-btn ui-icon-btn--ghost b05-routeedit__info-toggle" data-act="info-toggle"
aria-expanded="true" title="상태 · 범례 글판 접기 · 펴기">▾</button> aria-expanded="true" title="상태 · 범례 글판 접기 · 펴기">▾</button>
</div> </div>
</div> </div>
@@ -148,7 +148,10 @@ export function createRouteEditChrome(
<aside class="b05-routeedit__side-wrap"></aside>`; <aside class="b05-routeedit__side-wrap"></aside>`;
parent.append(overlay); parent.append(overlay);
// 부른 쪽 단추([3D])도 같은 유리 단추로 — 지도 위에 떠 있는 도구줄. // 부른 쪽 단추([3D])도 같은 유리 단추로 — 지도 위에 떠 있는 도구줄.
tools.forEach((tool) => tool.classList.replace("ui-btn--ghost", "ui-btn--glass")); tools.forEach((tool) => {
tool.classList.replace("ui-btn--ghost", "ui-btn--glass");
tool.setAttribute("data-size", "32"); // 도구줄 단 32(553-2)
});
overlay.querySelector('[data-slot="tools"]')!.replaceWith(...tools); overlay.querySelector('[data-slot="tools"]')!.replaceWith(...tools);
// 횡단 미리보기 = 패널 양식(382-1 · 손잡이 · 머리 · 폭 끌기 · 열림 · 폭 기억) · 자리는 지도 위 덮개 그대로 · // 횡단 미리보기 = 패널 양식(382-1 · 손잡이 · 머리 · 폭 끌기 · 열림 · 폭 기억) · 자리는 지도 위 덮개 그대로 ·
@@ -223,10 +226,8 @@ export function createRouteEditChrome(
lift.observe(info); lift.observe(info);
lift.observe(overlay.querySelector(".b05-routeedit__side-wrap")!); lift.observe(overlay.querySelector(".b05-routeedit__side-wrap")!);
const measureCloseButton = createButton({ label: "✕", variant: "ghost" }); const measureCloseButton = createIconButton({ glyph: "✕", title: "닫기", size: 24 });
measureCloseButton.classList.add("b05-routeedit__measure-close"); measureCloseButton.classList.add("b05-routeedit__measure-close");
measureCloseButton.setAttribute("aria-label", "닫기");
measureCloseButton.title = "닫기";
overlay.querySelector('[data-slot="measure-close"]')!.replaceWith(measureCloseButton); overlay.querySelector('[data-slot="measure-close"]')!.replaceWith(measureCloseButton);
const canvas = overlay.querySelector<HTMLCanvasElement>(".b05-routeedit__canvas")!; const canvas = overlay.querySelector<HTMLCanvasElement>(".b05-routeedit__canvas")!;
@@ -31,9 +31,9 @@ const FIT_MARGIN_M = 30;
export const RECOMMEND_MAP_HTML = ` export const RECOMMEND_MAP_HTML = `
<canvas class="b05-routerec__map"></canvas> <canvas class="b05-routerec__map"></canvas>
<div class="b05-routeedit__spin"> <div class="b05-routeedit__spin">
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-ccw" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-ccw"
title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button> title="반시계로 돌리기" aria-label="반시계로 돌리기">${HALF_TURN_ICON.ccw}</button>
<button type="button" class="ui-btn ui-btn--glass" data-act="rotate-cw" <button type="button" data-size="32" class="ui-btn ui-btn--glass" data-act="rotate-cw"
title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button> title="시계로 돌리기" aria-label="시계로 돌리기">${HALF_TURN_ICON.cw}</button>
</div>`; </div>`;
@@ -123,14 +123,14 @@ export function buildStructuresForm(options: {
const primary = document.createElement("button"); const primary = document.createElement("button");
primary.type = "button"; primary.type = "button";
primary.className = "b05-route__irregular-btn is-primary"; primary.className = "ui-btn ui-btn--filled b05-route__irregular-btn is-primary";
const removeButton = document.createElement("button"); const removeButton = document.createElement("button");
removeButton.type = "button"; removeButton.type = "button";
removeButton.className = "b05-route__irregular-btn is-danger"; removeButton.className = "ui-btn ui-btn--danger-ghost b05-route__irregular-btn is-danger";
removeButton.textContent = "삭제"; removeButton.textContent = "삭제";
const resetButton = document.createElement("button"); const resetButton = document.createElement("button");
resetButton.type = "button"; resetButton.type = "button";
resetButton.className = "b05-route__irregular-btn"; resetButton.className = "ui-btn ui-btn--ghost b05-route__irregular-btn";
resetButton.textContent = "리셋"; resetButton.textContent = "리셋";
const actions = document.createElement("div"); const actions = document.createElement("div");
actions.className = "b05-route__irregular-actions"; actions.className = "b05-route__irregular-actions";
@@ -249,7 +249,7 @@ export function renderStructureList(params: StructureListParams): void {
clear.className = "b05-route__irregular-clear"; clear.className = "b05-route__irregular-clear";
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = "b05-route__irregular-btn is-danger"; button.className = "ui-btn ui-btn--danger-ghost b05-route__irregular-btn is-danger";
button.textContent = "구조물 전체 삭제"; button.textContent = "구조물 전체 삭제";
button.addEventListener("click", params.onClearAll); button.addEventListener("click", params.onClearAll);
clear.append(button); clear.append(button);
@@ -40,7 +40,7 @@ export function buildSummaryRow(): SummaryRow {
name.dataset.b05Summary = "pick"; name.dataset.b05Summary = "pick";
const clear = document.createElement("button"); const clear = document.createElement("button");
clear.type = "button"; clear.type = "button";
clear.className = "b05-route__irregular-btn"; clear.className = "ui-btn ui-btn--ghost b05-route__irregular-btn";
clear.textContent = "해제"; clear.textContent = "해제";
clear.dataset.b05Summary = "clear"; clear.dataset.b05Summary = "clear";
const line = document.createElement("div"); const line = document.createElement("div");
@@ -69,7 +69,7 @@ export function createWideningBox(hooks: {
const on = !isRunOff(run, off); const on = !isRunOff(run, off);
const button = document.createElement("button"); const button = document.createElement("button");
button.type = "button"; button.type = "button";
button.className = `b05-route__irregular-btn${on ? " is-on" : ""}`; button.className = `ui-btn ui-btn--ghost b05-route__irregular-btn${on ? " is-on" : ""}`;
button.setAttribute("aria-pressed", String(on)); button.setAttribute("aria-pressed", String(on));
button.dataset.wideningRun = `${run.start_m}-${run.end_m}`; button.dataset.wideningRun = `${run.start_m}-${run.end_m}`;
const from = stationLabel(run.start_m, step); const from = stationLabel(run.start_m, step);
+12 -48
View File
@@ -197,10 +197,10 @@ html.is-bm-route .b05-route__view-controls {
} }
/* 방위 나침반을 놓을 자리 — ISO 버튼 바로 아래(2026-09-03 사용자 지시). /* 방위 나침반을 놓을 자리 — ISO 버튼 바로 아래(2026-09-03 사용자 지시).
버튼 줄 높이 34px + 위 여백 16px 아래에 붙인다. 위젯 모양·색은 공용 버튼 줄 높이 32px(단추 단 32) + 위 여백 16px 아래에 붙인다. 위젯 모양·색은 공용
(`ui_template/ui_template_compass.css`)이고 여기서는 위치만 준다. */ (`ui_template/ui_template_compass.css`)이고 여기서는 위치만 준다. */
.b05-route__compass { .b05-route__compass {
top: calc(var(--spacing-16) + 34px + var(--spacing-8)); top: calc(var(--spacing-16) + 32px + var(--spacing-8));
left: var(--spacing-16); left: var(--spacing-16);
} }
@@ -217,9 +217,9 @@ html.is-bm-route .b05-route__view-controls {
white-space: nowrap; white-space: nowrap;
} }
.b05-route__view-controls .ui-btn { /* 보기 줄 단추 = 부품 단추 단 32(553-2) · 줄 폭이 지도 폭을 넘지 않게 안 여백은 8 그대로 */
height: 34px; .b05-route__view-controls .ui-btn[data-size] {
padding: 0 var(--spacing-8); padding-inline: var(--spacing-8);
} }
.b05-route__view-separator { .b05-route__view-separator {
@@ -231,7 +231,7 @@ html.is-bm-route .b05-route__view-controls {
/* [3D 업데이트] 대기 표시 — 계획선 편집이 3D에 아직 안 들어갔음을 점으로 알린다. */ /* [3D 업데이트] 대기 표시 — 계획선 편집이 3D에 아직 안 들어갔음을 점으로 알린다. */
.b05-route__view-controls .ui-btn.is-pending { .b05-route__view-controls .ui-btn.is-pending {
position: relative; position: relative;
border-color: var(--color-warning, #d98a00); border-color: var(--color-warning);
} }
.b05-route__view-controls .ui-btn.is-pending::after { .b05-route__view-controls .ui-btn.is-pending::after {
@@ -242,7 +242,7 @@ html.is-bm-route .b05-route__view-controls {
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--color-warning, #d98a00); background: var(--color-warning);
} }
.b05-route__panel { .b05-route__panel {
@@ -356,30 +356,14 @@ html.is-bm-route .b05-route__view-controls {
gap: var(--spacing-8); gap: var(--spacing-8);
} }
/* [추가] · [삭제] · [리셋] = 부품 「단추」(553-2 · 주 filled · 위험 고스트 · 보조 ghost) — 크기 = 좌측 패널 양식 */
.b05-route__irregular-btn { .b05-route__irregular-btn {
flex: 1 1 0; flex: 1 1 0;
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface);
color: var(--color-text-body);
font-size: var(--text-caption);
cursor: pointer;
} }
.b05-route__irregular-btn.is-primary { /* 안 여백 8 그대로 — 옆 칸 폭 보호(구조물 조합 [해제]) */
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent); .b05-route__irregular-btn.ui-btn {
color: var(--color-text); padding-inline: var(--spacing-8);
}
.b05-route__irregular-btn.is-danger {
border-color: color-mix(in srgb, var(--color-danger) 50%, transparent);
color: var(--color-danger);
}
.b05-route__irregular-btn:disabled {
opacity: 0.45;
cursor: default;
} }
.b05-route__irregular-list { .b05-route__irregular-list {
@@ -590,27 +574,7 @@ html.is-bm-route .b05-route__view-controls {
margin-right: var(--spacing-8); margin-right: var(--spacing-8);
} }
.b05-route-profile__tool { /* 종단 도구 단추 = 부품 「단추」 유리 · 단 18(553-2 · `_Profile_Tools.ts`) */
padding: var(--btn-bar-padding);
border: 1px solid color-mix(in srgb, var(--color-border) 75%, transparent);
border-radius: 3px;
background: color-mix(in srgb, var(--color-surface-raised) 80%, transparent);
color: var(--color-text);
font-family: var(--font-body);
font-size: var(--btn-bar-font-size);
line-height: 1;
cursor: pointer;
}
.b05-route-profile__tool:disabled {
opacity: 0.35;
cursor: default;
}
.b05-route-profile__tool.is-active {
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 22%, transparent);
}
.b05-route-profile__tool-hint { .b05-route-profile__tool-hint {
color: var(--color-text-muted); color: var(--color-text-muted);
+9 -64
View File
@@ -87,28 +87,9 @@
pointer-events: auto; pointer-events: auto;
} }
/* 표시 토글 버튼 — B04 지도 레이어 버튼(.b04-map__layer-button--gis)과 같은 양식을 쓴다. /* 표시 토글 = 부품 「켜기」 칩 단 20(553-2 · 색 점 = 층 선 색) — 한 줄 유지 · 좁아져도 안 쭈그러듦. */
크기(패딩·글자)만 이 패널 기준을 유지한다(2026-08-01 사용자 지시). */
.b05-drainage__layer-button { .b05-drainage__layer-button {
/* 한 줄 유지 — 좁아져도 글자가 접히거나 쭈그러들지 않게 크기를 고정한다. */
flex: 0 0 auto; flex: 0 0 auto;
white-space: nowrap;
padding: 2px var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface);
color: var(--color-text-secondary);
font-size: var(--text-caption);
opacity: 0.55;
cursor: pointer;
}
/* 켜진 레이어는 그 레이어의 선 색을 테두리·글자·안쪽 링에 그대로 쓴다(지도와 바로 대조). */
.b05-drainage__layer-button.is-active {
border-color: var(--b05-layer-color, var(--color-border));
box-shadow: inset 0 0 0 1px var(--b05-layer-color, transparent);
color: var(--b05-layer-color, var(--color-text-body));
opacity: 1;
} }
/* 배관 우클릭 메뉴 — 뷰포트 기준 절대 위치(B04 지도와 같은 조작) · 모양은 공용 `.ui-ctx-menu`. */ /* 배관 우클릭 메뉴 — 뷰포트 기준 절대 위치(B04 지도와 같은 조작) · 모양은 공용 `.ui-ctx-menu`. */
@@ -156,21 +137,9 @@
pointer-events: none; pointer-events: none;
} }
/* 머리 줄 단추 = 부품 「단추」 단 22(553-2) — 자리만 */
.b05-drainage__analyze { .b05-drainage__analyze {
margin-left: auto; margin-left: auto;
padding: 2px var(--spacing-8);
border: 1px solid
color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent);
border-radius: var(--radius-inputs);
background: var(--color-surface);
color: var(--color-text-body);
font-size: var(--text-caption);
cursor: pointer;
}
.b05-drainage__analyze:disabled {
opacity: 0.45;
cursor: default;
} }
/* 머리 줄 둘째 단추([자동 제안]) — [업데이트] 우측에 나란히(auto 마진 해제). */ /* 머리 줄 둘째 단추([자동 제안]) — [업데이트] 우측에 나란히(auto 마진 해제). */
@@ -178,21 +147,11 @@
margin-left: var(--spacing-4, 4px); margin-left: var(--spacing-4, 4px);
} }
/* 배관 편집 토글 활성 상태. */
.b05-drainage__analyze.is-active {
background: color-mix(
in srgb,
var(--color-royal-amethyst, rgb(109 40 217)) 18%,
var(--color-surface)
);
border-color: var(--color-royal-amethyst, rgb(109 40 217));
}
/* 유역 제원 목록 — 면적·유역표고·유하거리·관경(수식 확정 전까지 "미정"). */ /* 유역 제원 목록 — 면적·유역표고·유하거리·관경(수식 확정 전까지 "미정"). */
/* 유역 목록은 3행까지만 보이고 나머지는 스크롤한다(2026-08-01 사용자 지시). /* 유역 목록은 3행까지만 보이고 나머지는 스크롤한다(2026-08-01 사용자 지시).
높이를 비율로 잡으면 유역 수와 무관하게 잘려 몇 개인지 가늠이 안 된다. */ 높이를 비율로 잡으면 유역 수와 무관하게 잘려 몇 개인지 가늠이 안 된다. */
.b05-drainage__basins { .b05-drainage__basins {
--b05-basin-row: 34px; --b05-basin-row: 32px;
display: flex; display: flex;
max-height: calc(3 * var(--b05-basin-row) + 2 * 2px + 2 * var(--spacing-8)); max-height: calc(3 * var(--b05-basin-row) + 2 * 2px + 2 * var(--spacing-8));
@@ -217,25 +176,11 @@
display: none; display: none;
} }
.b05-drainage__basin { /* 유역 줄 = 부품 「목록 줄」 단 32(553-2) — 번호 원 · 제원 왼쪽 붙임 · 글 12(제원 한 줄) */
display: flex; .b05-drainage__basin.ui-list-row {
min-height: var(--b05-basin-row); flex: none;
align-items: center; justify-content: flex-start;
gap: var(--spacing-8);
padding: var(--spacing-4) var(--spacing-8);
border: 1px solid transparent;
border-radius: var(--radius-inputs);
background: none;
color: var(--color-text-body);
font-size: var(--text-caption); font-size: var(--text-caption);
text-align: left;
cursor: pointer;
}
.b05-drainage__basin:hover,
.b05-drainage__basin.is-selected {
border-color: var(--color-border);
background: var(--color-surface);
} }
/* 지도 위 서클 번호와 같은 파스텔 색을 써서 목록 항목과 유역을 눈으로 잇는다. */ /* 지도 위 서클 번호와 같은 파스텔 색을 써서 목록 항목과 유역을 눈으로 잇는다. */
@@ -270,7 +215,7 @@
/* 배수유역 낡음(326-6) — 옛 그림 그대로 · [업데이트] 단추에 점([3D 업데이트] 꼴) · 구석 「계산 중」. */ /* 배수유역 낡음(326-6) — 옛 그림 그대로 · [업데이트] 단추에 점([3D 업데이트] 꼴) · 구석 「계산 중」. */
.b05-drainage__update.is-pending { .b05-drainage__update.is-pending {
position: relative; position: relative;
border-color: var(--color-warning, #d98a00); border-color: var(--color-warning);
} }
.b05-drainage__update.is-pending::after { .b05-drainage__update.is-pending::after {
@@ -281,7 +226,7 @@
width: 6px; width: 6px;
height: 6px; height: 6px;
border-radius: 50%; border-radius: 50%;
background: var(--color-warning, #d98a00); background: var(--color-warning);
} }
.b05-drainage__busy { .b05-drainage__busy {
+6 -23
View File
@@ -63,7 +63,7 @@
/* 조작 설명 — 도구줄 아래 왼쪽 · 기본 접힘 · [?] 로 펼침(345-5 덧 3 · 352-1) · 펼치면 **2열**(사용자 지시 ⑩). */ /* 조작 설명 — 도구줄 아래 왼쪽 · 기본 접힘 · [?] 로 펼침(345-5 덧 3 · 352-1) · 펼치면 **2열**(사용자 지시 ⑩). */
.b05-routeedit__hint { .b05-routeedit__hint {
top: calc(var(--spacing-16) + 34px + var(--spacing-8)); top: calc(var(--spacing-16) + 32px + var(--spacing-8));
left: var(--spacing-16); left: var(--spacing-16);
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@@ -72,11 +72,10 @@
pointer-events: none; pointer-events: none;
} }
.b05-routeedit__hint-toggle { .b05-routeedit__hint-toggle.ui-btn[data-size] {
flex: none; flex: none;
padding: var(--spacing-4, 4px) var(--spacing-8); padding-inline: var(--spacing-8);
font-weight: 600; font-weight: 600;
line-height: 1;
pointer-events: auto; pointer-events: auto;
} }
@@ -132,22 +131,12 @@
display: block; display: block;
} }
/* 접기 = 부품 「아이콘 단추」 단 18(553-2) */
.b05-routeedit__info-toggle { .b05-routeedit__info-toggle {
grid-column: -2; grid-column: -2;
padding: 0 2px;
border: 0;
background: none;
color: inherit;
font: inherit;
line-height: inherit;
cursor: pointer;
pointer-events: auto; pointer-events: auto;
} }
.b05-routeedit__info-toggle:hover {
color: var(--color-text-body);
}
/* 회전 단추 — 지도 오른쪽 위(사용자 지시 ⑯) · 횡단 판 왼쪽에 앉음(352-1). 여기만 클릭을 받는다. */ /* 회전 단추 — 지도 오른쪽 위(사용자 지시 ⑯) · 횡단 판 왼쪽에 앉음(352-1). 여기만 클릭을 받는다. */
.b05-routeedit__spin { .b05-routeedit__spin {
position: absolute; position: absolute;
@@ -158,12 +147,6 @@
gap: var(--spacing-8); gap: var(--spacing-8);
} }
.b05-routeedit__spin .ui-btn {
padding: var(--spacing-8) var(--spacing-12);
font-size: var(--text-body);
line-height: 1;
}
.b05-routeedit__legend { .b05-routeedit__legend {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -192,7 +175,7 @@
.b05-routeedit__side-wrap { .b05-routeedit__side-wrap {
position: absolute; position: absolute;
z-index: 2; z-index: 2;
top: calc(var(--spacing-16) + 34px + var(--spacing-8)); /* 도구줄 · 회전 단추 줄 아래 */ top: calc(var(--spacing-16) + 32px + var(--spacing-8)); /* 도구줄(단 32) · 회전 단추 줄 아래 */
right: var(--spacing-12); right: var(--spacing-12);
bottom: calc(var(--spacing-12) + var(--b05-profile-cover, 0px)); bottom: calc(var(--spacing-12) + var(--b05-profile-cover, 0px));
display: flex; display: flex;
@@ -377,7 +360,7 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: var(--spacing-4, 4px); gap: var(--spacing-4, 4px);
height: 34px; height: 32px;
padding: 0 var(--spacing-8); padding: 0 var(--spacing-8);
border: 1px solid color-mix(in srgb, var(--color-border) 65%, transparent); border: 1px solid color-mix(in srgb, var(--color-border) 65%, transparent);
border-radius: var(--radius-8, 6px); border-radius: var(--radius-8, 6px);
+14 -28
View File
@@ -284,15 +284,15 @@
align-items: stretch; align-items: stretch;
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 1px solid var(--color-border, #3a3f4a); border: 1px solid var(--color-border);
border-radius: var(--radius-inputs, 4px); border-radius: var(--radius-inputs);
background: var(--color-surface); background: var(--color-surface);
gap: 0; gap: 0;
overflow: hidden; overflow: hidden;
} }
.b05-structure__stepper:focus-within { .b05-structure__stepper:focus-within {
border-color: var(--color-royal-amethyst, rgb(109 40 217)); border-color: var(--color-accent);
} }
/* 가운데 숫자칸은 자기 테두리·배경을 버리고 덩어리 안에 녹는다. */ /* 가운데 숫자칸은 자기 테두리·배경을 버리고 덩어리 안에 녹는다. */
@@ -309,32 +309,22 @@
outline: none; outline: none;
} }
/* 좌우 끝 버튼 — 덩어리 안에서는 가르는 선 하나씩만 갖는다. */ /* 좌우 끝 버튼 = 부품 「아이콘 단추」(553-2) — 덩어리 안에서는 가르는 선 하나씩만 갖는다. */
.b05-structure__step { .b05-structure__stepper > .b05-structure__step {
flex: none;
/* 손가락·마우스로 집기 쉽게 넓힌다(2026-08-29 사용자 지시). 숫자칸이 남는 폭을 /* 손가락·마우스로 집기 쉽게 넓힌다(2026-08-29 사용자 지시). 숫자칸이 남는 폭을
차지하므로 이 값이 곧 버튼 폭이다. */ 차지하므로 이 값이 곧 버튼 폭이다. */
width: 28px; width: 28px;
padding: 0; height: 100%;
border: 0; border: 0;
border-radius: 0; border-radius: 0;
background: transparent;
color: var(--color-text-body);
font-size: 12px;
line-height: 1;
cursor: pointer;
} }
.b05-structure__step:first-child { .b05-structure__stepper > .b05-structure__step:first-child {
border-right: 1px solid var(--color-border, #3a3f4a); border-right: 1px solid var(--color-border);
} }
.b05-structure__step:last-child { .b05-structure__stepper > .b05-structure__step:last-child {
border-left: 1px solid var(--color-border, #3a3f4a); border-left: 1px solid var(--color-border);
}
.b05-structure__step:hover {
background: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 14%, transparent);
} }
/* 구조물 배치 폼의 숫자 칸 — 브라우저 기본 증감 화살표(스피너)를 없앤다 /* 구조물 배치 폼의 숫자 칸 — 브라우저 기본 증감 화살표(스피너)를 없앤다
@@ -389,10 +379,6 @@
height: var(--b05-control-h); height: var(--b05-control-h);
} }
.b05-structure__step {
height: 100%;
}
/* 조정창에서 옮겨 온 행(단 수·옵션) — 같은 변수로 맞춘다. */ /* 조정창에서 옮겨 온 행(단 수·옵션) — 같은 변수로 맞춘다. */
.b05-structure__adjust-slot .b06-structure-panel__controls { .b05-structure__adjust-slot .b06-structure-panel__controls {
height: var(--b05-control-h); height: var(--b05-control-h);
@@ -634,10 +620,10 @@
font-size: var(--text-caption); font-size: var(--text-caption);
} }
/* 켬 = 확폭 포함 — 테두리색 + 표식(`.is-primary` 는 [추가] 단추 몫이라 안 씀) */ /* 켬 = 확폭 포함 — 부품 보조 단추 꼴 + 표식 · 끔 = 흐린 선 · 글(`.is-primary` 는 [추가] 단추 몫이라 안 씀) */
.b05-structure__widening .b05-route__irregular-btn.is-on { .b05-structure__widening .b05-route__irregular-btn[aria-pressed="false"] {
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent); border-color: var(--color-border);
color: var(--color-text); color: var(--color-text-secondary);
} }
.b05-structure__widening [data-widening-run][aria-pressed="true"]::before { .b05-structure__widening [data-widening-run][aria-pressed="true"]::before {
+14 -52
View File
@@ -413,34 +413,15 @@
pointer-events: none; pointer-events: none;
} }
/* 상시 보이게 둔다(예전엔 패널 hover 시에만 노출). 크기는 ▲▼·↺·⬆⬇ 전부 21×17로 /* 상시 보이게 둔다(예전엔 패널 hover 시에만 노출). ▲▼·↺·⬆⬇ = 부품 「아이콘 단추」 선 꼴 단 18(553-2 · 18×18). */
키웠다(2026-08-04 사용자 지시 — 18×15는 누르기도 읽기도 작았다). */
.b05-profile-edit__btn { .b05-profile-edit__btn {
position: absolute; position: absolute;
width: 21px;
height: 17px;
padding: 0;
font-size: 12px;
line-height: 1;
border: 1px solid color-mix(in srgb, var(--color-border) 75%, transparent);
border-radius: 3px;
background: color-mix(in srgb, var(--color-surface-raised) 80%, transparent);
color: var(--color-text);
font-size: 9px;
line-height: 1;
cursor: pointer;
opacity: 0.9; opacity: 0.9;
pointer-events: auto; pointer-events: auto;
transition:
opacity var(--transition-fast),
background var(--transition-fast);
} }
.b05-route-profile .b05-profile-edit__btn:hover, .b05-route-profile .b05-profile-edit__btn:hover,
.b05-route-profile .b05-profile-edit__btn:focus-visible { .b05-route-profile .b05-profile-edit__btn:focus-visible {
border-color: var(--color-border);
background: var(--color-surface-raised);
color: var(--color-text);
opacity: 1; opacity: 1;
} }
@@ -470,8 +451,8 @@
} }
.b05-profile-edit__btn.is-down { .b05-profile-edit__btn.is-down {
border-color: color-mix(in srgb, var(--color-chart-0, #5b8def) 65%, transparent); border-color: color-mix(in srgb, var(--color-chart-0) 65%, transparent);
color: var(--color-chart-0, #5b8def); color: var(--color-chart-0);
} }
.b05-route-profile .b05-profile-edit__btn.is-up:hover, .b05-route-profile .b05-profile-edit__btn.is-up:hover,
@@ -483,16 +464,15 @@
.b05-route-profile .b05-profile-edit__btn.is-down:hover, .b05-route-profile .b05-profile-edit__btn.is-down:hover,
.b05-route-profile .b05-profile-edit__btn.is-down:focus-visible { .b05-route-profile .b05-profile-edit__btn.is-down:focus-visible {
border-color: var(--color-chart-0, #5b8def); border-color: var(--color-chart-0);
background: color-mix(in srgb, var(--color-chart-0, #5b8def) 14%, var(--color-surface-raised)); background: color-mix(in srgb, var(--color-chart-0) 14%, var(--color-surface-raised));
color: var(--color-chart-0, #5b8def); color: var(--color-chart-0);
} }
.b05-profile-edit__btn.is-reset { .b05-profile-edit__btn.is-reset {
top: 22px; top: 22px;
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent); border-color: color-mix(in srgb, var(--color-accent) 55%, transparent);
background: var(--color-surface-raised); color: var(--color-accent);
color: color-mix(in srgb, var(--color-royal-amethyst, rgb(139 92 246)) 85%, var(--color-text));
opacity: 0.95; opacity: 0.95;
} }
.b05-profile-table__segment.is-structure { .b05-profile-table__segment.is-structure {
@@ -528,30 +508,12 @@
margin-left: auto; margin-left: auto;
} }
.b05-profile__zoom-btn { /* 묶음 안 단추 = 부품 「아이콘 단추」 단 22(553-2) — 붙은 묶음이라 둥글기 없이 가르는 선 하나씩 ·
width: 22px; * 배율 한계(축소는 폭맞춤, 확대는 8배)에 닿은 단추는 부품 막힘 꼴(흐림). */
height: 22px; .b05-profile__zoom > .b05-profile__zoom-btn {
padding: 0; border-radius: 0;
border: none;
border-left: 1px solid var(--color-border);
background: none;
color: var(--color-text-secondary);
font-size: 0.8rem;
line-height: 1;
cursor: pointer;
} }
.b05-profile__zoom-btn:first-child { .b05-profile__zoom-btn + .b05-profile__zoom-btn {
border-left: none; border-left-color: var(--color-border);
}
.b05-profile__zoom-btn:hover:not(:disabled) {
color: var(--color-text);
background: var(--color-surface);
}
/* 배율 한계(축소는 폭맞춤, 확대는 8배)에 닿은 버튼 — 눌러도 변화가 없으니 흐리게 죽인다. */
.b05-profile__zoom-btn:disabled {
opacity: 0.35;
cursor: default;
} }
@@ -25,6 +25,11 @@ function el(tag, className = "") {
parent: null, parent: null,
listeners: {}, listeners: {},
style: { height: "", cssText: "" }, style: { height: "", cssText: "" },
attrs: {},
setAttribute(k, v) {
node.attrs[k] = String(v);
},
getAttribute: (k) => node.attrs[k] ?? null,
}; };
node.classList = { node.classList = {
list: () => node.className.split(/\s+/).filter(Boolean), list: () => node.className.split(/\s+/).filter(Boolean),
+3 -1
View File
@@ -151,7 +151,9 @@ def test_도구줄_직선화_오른쪽_자유지정():
tools = _read("B05_Profile/B05_Profile_UI_Profile_Tools.ts") tools = _read("B05_Profile/B05_Profile_UI_Profile_Tools.ts")
assert '"자유지정"' in tools assert '"자유지정"' in tools
assert "wrap.append(reset, undo, redo, straighten, free, shift, up, down)" in tools assert "wrap.append(reset, undo, redo, straighten, free, shift, up, down)" in tools
assert 'className = "b05-route-profile__tool"' in tools # 같은 단추 양식 assert (
'className = "ui-btn ui-btn--glass b05-route-profile__tool"' in tools
) # 같은 단추 양식(부품 단추 · 553-2)
def test_적용은_직선화와_같은_길_키_배선(): def test_적용은_직선화와_같은_길_키_배선():
@@ -102,6 +102,9 @@ def test_배선과_양식():
assert "attachSubPanelPopouts(profile);" in shared assert "attachSubPanelPopouts(profile);" in shared
css = (ROOT / "A00_Common/design/design_page.css").read_text(encoding="utf-8") css = (ROOT / "A00_Common/design/design_page.css").read_text(encoding="utf-8")
block = re.search(r"\n\.b05-subpanel__popout \{([^}]*)\}", css).group(1) block = re.search(r"\n\.b05-subpanel__popout \{([^}]*)\}", css).group(1)
assert "var(--btn-bar-padding)" in block and "var(--btn-bar-font-size)" in block # 크기 = 부품 단추 단 18(553-2) — 페이지 CSS 여백 덮어쓰기 없음
assert "flex: none;" in block and "padding" not in block
popout = (ROOT / "A00_Common/design/design_profile_popout.ts").read_text(encoding="utf-8")
assert popout.count('button.setAttribute("data-size", "18")') == 2
assert ".b05-profile__masshaul-overlay.is-popout," in css assert ".b05-profile__masshaul-overlay.is-popout," in css
assert ".b05-drainage.is-popout {" in css assert ".b05-drainage.is-popout {" in css
@@ -35,7 +35,7 @@ def test_도움말_기본_접힘_펼침_기억() -> None:
# 접힘 = 몸통 없음 · 펼침 = 2열 · 단추만 클릭 받음 # 접힘 = 몸통 없음 · 펼침 = 2열 · 단추만 클릭 받음
assert "display: none;" in _rule(".b05-routeedit__hint-body") assert "display: none;" in _rule(".b05-routeedit__hint-body")
assert "display: grid;" in _rule(".b05-routeedit__hint.is-open > .b05-routeedit__hint-body") assert "display: grid;" in _rule(".b05-routeedit__hint.is-open > .b05-routeedit__hint-body")
assert "pointer-events: auto;" in _rule(".b05-routeedit__hint-toggle") assert "pointer-events: auto;" in _rule(".b05-routeedit__hint-toggle.ui-btn[data-size]")
assert "pointer-events: none;" in _rule(".b05-routeedit__hint") assert "pointer-events: none;" in _rule(".b05-routeedit__hint")