feat(B06): 구간형 구조물 범위 안 횡단 카드 전부 강조 · B05·B06 화면 글 기슭막이 → 흙막이(106-1·106-2)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
This commit is contained in:
2026-10-08 16:11:44 +09:00
co-authored by Claude Sonnet 5.5
parent 331883043e
commit 4885557976
15 changed files with 158 additions and 55 deletions
@@ -330,7 +330,7 @@ export function createFacilityOptionsForm(
if (c.id === "inlet" && item.id === "structure" && inletIsAuto()) {
const drawn = drawnNow();
return drawn
? { value: drawn.basin ? "집수정" : "기슭막이", tag: "자동" }
? { value: drawn.basin ? "집수정" : "흙막이", tag: "자동" }
: { value: "", tag: "자동(지형 규칙)" };
}
// 빈 고르기 칸 = 「선택 안 함」 그대로(54-39 · 기본값 꼬리 없음)
@@ -412,7 +412,7 @@ export function createFacilityOptionsForm(
// ── 추가 기슭막이(다단) — 횡단도에서 다단 벽을 고를 때만 뜬다(2026-08-30 사용자
// 지시 4). 유입구·유출구와 같은 양식이되, 값은 조정창 사본의 행이 그대로 옮겨 온다
// (다단 벽은 폼에 자기 칸이 없다 — 높이·형태·구간값이 전부 조정 채널에 있다).
const extraGroup = group("추가 기슭막이");
const extraGroup = group("추가 흙막이");
const extraSlot = document.createElement("div");
extraSlot.className = "b05-structure__adjust-slot";
extraGroup.body.append(extraSlot);
+7 -7
View File
@@ -124,14 +124,14 @@ export function appendCulvertOverlay(
segment.points,
`유출부 성토부선 — 사면길이 ${segment.lengthM.toFixed(2)}m` +
(segment.overLimit
? " (법정 5m 이상 — 기슭막이 의무 구간, 조정창에서 추가)"
? " (법정 5m 이상 — 흙막이 의무 구간, 조정창에서 추가)"
: " (법정 5m 이내)") +
` · 성토 물매 1:${(segment.ratio ?? layout.fillSlope.ratio).toFixed(2)}` +
(layout.fillSlope.roadWideningM > 0.01
? ` · 노폭 연장 ${layout.fillSlope.roadWideningM.toFixed(2)}m`
: "") +
(layout.fillSlope.structureRequired
? " · ⚠ 기슭막이로 성토를 못 받는 자리 — 옹벽·석축 검토(성토_비탈면 §2)"
? " · ⚠ 흙막이로 성토를 못 받는 자리 — 옹벽·석축 검토(성토_비탈면 §2)"
: ""),
);
}
@@ -148,7 +148,7 @@ export function appendCulvertOverlay(
? `${basinFillName} 3도 절토선 — 구조물이 원지반에 묻혀 성토 불필요, 원지반 교차까지 ${segment.lengthM.toFixed(2)}m`
: `${basinFillName} 성토부선 — 사면길이 ${segment.lengthM.toFixed(2)}m` +
(segment.overLimit
? " (법정 5m 이상 — 기슭막이 의무 구간, 조정창에서 추가)"
? " (법정 5m 이상 — 흙막이 의무 구간, 조정창에서 추가)"
: " (법정 5m 이내)") +
` · 성토 물매 1:${(segment.ratio ?? 1.2).toFixed(2)}`,
);
@@ -192,7 +192,7 @@ export function appendCulvertOverlay(
"b06-chart__culvert-revet",
// 높이 표기 = 순수 높이(바닥~상단, 근입 0.5 포함). 추가 기슭막이도 같은 기준으로
// 통일했다(2026-08-23 배관 벽 → 2026-08-29 추가 벽).
`${roleLabel(wall.role)} 기슭막이 ${wall.form ?? ""} H=${(
`${roleLabel(wall.role)} 흙막이 ${wall.form ?? ""} H=${(
wall.height + REVET_EMBED_DEPTH_M
).toFixed(1)}m` +
`(상단 = 사면선 접점, 전면 1:${wall.lean}(품셈 13-4-4 [주]⑪ 표준경사))` +
@@ -221,8 +221,8 @@ export function appendCulvertOverlay(
basin.reason === "cut"
? "절토측 유입 — 집수정 기본(성토사면 없음)"
: basin.reason === "manual"
? "사용자 선택 집수정(규칙상 기슭막이 자리)"
: "유입측 성토사면 3m 이하 — 기슭막이 대신 집수정 기본";
? "사용자 선택 집수정(규칙상 흙막이 자리)"
: "유입측 성토사면 3m 이하 — 흙막이 대신 집수정 기본";
for (const part of basin.parts) {
const shape = polygon(
part.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]),
@@ -344,7 +344,7 @@ export function appendCulvertOverlay(
const hit = polygon(
wall.points.map((p) => [x(p.offset), toDisplayY(p.elevation)] as [number, number]),
"b06-chart__culvert-revet-hit",
`${roleLabel(wall.role)} 기슭막이 선택 — 카드 하단 ◀/▶로 자리 조절`,
`${roleLabel(wall.role)} 흙막이 선택 — 카드 하단 ◀/▶로 자리 조절`,
);
hit.addEventListener("click", (event) => {
event.stopPropagation();
@@ -407,14 +407,13 @@ export function buildStructurePanel(
return { button, set };
};
const LINK_TITLE =
"옆 측점 기슭막이와 횡단도 위치를 함께 쓴다. 끄면 이 측점 위치를 따로 잡는다 — 구조물이 늘지는 않는다.";
"옆 측점 흙막이와 횡단도 위치를 함께 쓴다. 끄면 이 측점 위치를 따로 잡는다 — 구조물이 늘지는 않는다.";
/** 경사 해제(수평) 중에는 연동을 못 푼다 — 기슭막이 Z를 하나로 묶기 때문(2026-08-30). */
const LINK_LOCKED_TITLE =
"경사 해제(수평) 중에는 연동을 풀 수 없다 — 기슭막이 Z를 하나로 묶는다.";
const LINK_LOCKED_TITLE = "경사 해제(수평) 중에는 연동을 풀 수 없다 — 흙막이 Z를 하나로 묶는다.";
const linkToggle = makeToggle("연동", LINK_TITLE, (on) => deps.setLinked(on));
const gradeToggle = makeToggle(
"경사",
"종단경사 반영(3D 전용) — 켜면 3D 기슭막이가 종단경사를 따라 서고, 끄면 연장 구간 내내 Z를 동일하게 본다. 횡단도는 언제나 그 측점 계획고로 그린다.",
"종단경사 반영(3D 전용) — 켜면 3D 흙막이가 종단경사를 따라 서고, 끄면 연장 구간 내내 Z를 동일하게 본다. 횡단도는 언제나 그 측점 계획고로 그린다.",
(on) => deps.setFollowGrade(on),
);
+33 -3
View File
@@ -19,6 +19,7 @@ import type {
HaulEquipmentLimit,
SectionDetailResponse,
} from "./B06_Section_Api_Fetch";
import type { StructureInstance } from "../B05_Profile/B05_Profile_Api_Structures";
import type { RockBoundaryControl } from "./B06_Section_UI_Cross_Design";
import type { CrossAreaKey } from "./B06_Section_UI_Cross_Areas";
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
@@ -197,6 +198,26 @@ export function createSectionView(
};
if (revetOffset) revetOffset.onSelectionChange = syncRevetHighlights;
/**
* 고른 구간형 구조물(노면공 등)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
* 가장 가까운 한 장의 `--selected` 는 그대로 — 클래스만 갈아 끼워 줌·팬을 지킨다.
*/
let spanMarks: ReadonlyArray<StructureInstance> = [];
let spanMarkId: string | null = null;
const inSpan = (section: CrossSection): boolean => {
const mark = spanMarkId ? spanMarks.find((entry) => entry.structure_id === spanMarkId) : null;
const { start_m: start, end_m: end } = mark ?? {};
if (start == null || end == null || start === end) return false;
const at = section.chainage_m;
return at >= Math.min(start, end) - 0.01 && at <= Math.max(start, end) + 0.01;
};
const syncSpanHighlights = (): void => {
for (const section of currentDetail?.cross_sections ?? [])
document
.getElementById(`cross-${section.station_id}`)
?.classList.toggle("b06-cross-card--span", inSpan(section));
};
/** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */
let stationSelectListener: ((stationId: string | null) => void) | null = null;
@@ -269,8 +290,8 @@ export function createSectionView(
section: CrossSection,
forcedHeightPx?: number,
plotBase?: CrossPlotBase | null,
): HTMLElement =>
createCrossSectionCard(
): HTMLElement => {
const card = createCrossSectionCard(
section,
section.station_id === selectedStationId,
currentExaggeration,
@@ -296,6 +317,9 @@ export function createSectionView(
plotBase,
cutSlope,
);
card.classList.toggle("b06-cross-card--span", inSpan(section));
return card;
};
/**
* 옆 측점 구조물이 이 측점까지 넘어왔는지 — 기슭막이 기본 연장 10m(전 5·후 5)면
@@ -483,7 +507,11 @@ export function createSectionView(
);
}
},
selectMark: (structureId) => profile.selectMark(structureId),
selectMark: (structureId) => {
spanMarkId = structureId;
syncSpanHighlights();
profile.selectMark(structureId);
},
setStationSelectListener(listener) {
stationSelectListener = listener;
},
@@ -491,6 +519,8 @@ export function createSectionView(
structureEdit = edit;
},
setStructureMarks(structures, types) {
spanMarks = structures;
syncSpanHighlights();
profile.setMarks(structures, types);
// 검증용 훅 — 알약 레인이 무엇을 받았는지 화면 밖에서 수치로 본다
// (`__corridorBuild` 등과 같은 용도).
@@ -175,6 +175,12 @@
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-danger) 45%, transparent);
}
/* 구간형 구조물(노면공 등)을 고르면 시작~종료 안 모든 카드(106-1) — 가까운 한 장의 선택 강조보다 옅게. */
.b06-cross-card--span:not(.b06-cross-card--selected) {
border-color: var(--color-danger);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-danger) 45%, transparent);
}
/* 제목행 1행 구조(E-2/E-3): 측점 라벨 → 단면유형 pill → 지반유형 → 구조물·kind. */
.b06-cross-card > header {
display: flex;
+12 -12
View File
@@ -160,7 +160,7 @@
},
{
"key": "inlet_revet_form",
"label": "유입 기슭막이 형태",
"label": "유입 흙막이 형태",
"input": "select",
"choices": [
"돌쌓기(찰)",
@@ -176,7 +176,7 @@
},
{
"key": "inlet_revet_length_m",
"label": "유입 기슭막이 길이",
"label": "유입 흙막이 길이",
"input": "number",
"unit": "m",
"default": 10,
@@ -184,7 +184,7 @@
},
{
"key": "inlet_revet_before_m",
"label": "유입 기슭막이 기준측점 전",
"label": "유입 흙막이 기준측점 전",
"input": "number",
"unit": "m",
"default": 5,
@@ -193,7 +193,7 @@
},
{
"key": "inlet_revet_after_m",
"label": "유입 기슭막이 기준측점 후",
"label": "유입 흙막이 기준측점 후",
"input": "number",
"unit": "m",
"default": 5,
@@ -202,7 +202,7 @@
},
{
"key": "inlet_revet_height_m",
"label": "유입 기슭막이 높이",
"label": "유입 흙막이 높이",
"input": "number",
"unit": "m",
"default": 1.5,
@@ -223,7 +223,7 @@
},
{
"key": "outlet_revet_form",
"label": "유출 기슭막이 형태",
"label": "유출 흙막이 형태",
"input": "select",
"choices": [
"돌쌓기(찰)",
@@ -240,7 +240,7 @@
},
{
"key": "outlet_revet_length_m",
"label": "유출 기슭막이 길이",
"label": "유출 흙막이 길이",
"input": "number",
"unit": "m",
"default": 10,
@@ -248,7 +248,7 @@
},
{
"key": "outlet_revet_before_m",
"label": "유출 기슭막이 기준측점 전",
"label": "유출 흙막이 기준측점 전",
"input": "number",
"unit": "m",
"default": 5,
@@ -257,7 +257,7 @@
},
{
"key": "outlet_revet_after_m",
"label": "유출 기슭막이 기준측점 후",
"label": "유출 흙막이 기준측점 후",
"input": "number",
"unit": "m",
"default": 5,
@@ -266,7 +266,7 @@
},
{
"key": "outlet_revet_height_m",
"label": "유출 기슭막이 높이",
"label": "유출 흙막이 높이",
"input": "number",
"unit": "m",
"default": 1.5,
@@ -277,7 +277,7 @@
},
{
"key": "revet_foundation",
"label": "기슭막이 기초",
"label": "흙막이 기초",
"input": "select",
"choices": [
"기초유",
@@ -317,7 +317,7 @@
},
{
"key": "revet_foundation",
"label": "기슭막이 기초",
"label": "흙막이 기초",
"input": "select",
"choices": [
"기초유",
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
"""PLAN 106 — 구간형 구조물 범위 강조 · B05/B06 화면 글 「흙막이」.
• 106-1 구조물을 고르면 시작~종료 안 횡단 카드 전부에 `--span` 클래스 · 카드를 다시 만들어도 유지
• 106-2 로케일 · 툴팁 · 정의 label 에 「기슭막이」 없음(독립=D군 · 사방 근거 글 빼고) · 저장 값 · choices 그대로
"""
import json
import os
import re
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
def _read(*parts: str) -> str:
with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle:
return handle.read()
VIEW = _read("B06_Section", "B06_Section_UI_Section_View.ts")
def test_범위_강조_클래스는_고름_목록갱신_카드재생성_세_길에서_칠한다():
assert VIEW.count("b06-cross-card--span") == 2 # syncSpanHighlights · buildCrossCard
assert re.search(r"selectMark: \(structureId\) => \{\s+spanMarkId = structureId;", VIEW)
assert re.search(r"spanMarks = structures;\s+syncSpanHighlights\(\);", VIEW)
assert 'card.classList.toggle("b06-cross-card--span", inSpan(section))' in VIEW
def test_범위_판정은_시작_종료가_다른_구간형만():
assert "start == null || end == null || start === end" in VIEW
def test_범위_강조_CSS는_선택_강조를_덮지_않는다():
css = _read("B06_Section", "B06_Section_UI_Style_Cross.css")
assert ".b06-cross-card--span:not(.b06-cross-card--selected)" in css
def test_로케일에_기슭막이는_독립_한_곳뿐():
locale = _read("ui_template", "ui_template_locale_b2.ts")
korean = [
line
for line in locale.splitlines()
if "기슭막이" in line and not line.strip().startswith(("//", "/*"))
]
assert len(korean) == 1 and "B06_Cross_Revet_Own" in korean[0]
def test_정의_label은_흙막이_choices_값은_그대로():
definition = json.loads(_read("resources", "master_template", "구조물정의.json"))
labels, choices = [], []
def walk(node):
if isinstance(node, dict):
if isinstance(node.get("label"), str):
labels.append(node["label"])
if node.get("key") in ("inlet_type", "outlet_type"):
choices.append(node["choices"])
for value in node.values():
walk(value)
elif isinstance(node, list):
for value in node:
walk(value)
walk(definition)
assert not [label for label in labels if "기슭막이" in label and label != "기슭막이"]
assert ["기슭막이", "집수정"] in choices and ["기슭막이"] in choices
@@ -120,7 +120,7 @@
out.splitAfter = ctl("inlet", "after").value;
await set("inlet", "after", "4");
out.splitLength = ctl("inlet", "length").value;
// 흙막이 = 유출 「추가 기슭막이(단)」 수만큼(54-1) — [추가] · [빼기] 없음 · 도수로/돌붙임 하나
// 흙막이 = 유출 「추가 흙막이(단)」 수만큼(54-1) — [추가] · [빼기] 없음 · 도수로/돌붙임 하나
out.soilAddButtons = spec().querySelectorAll('[data-spec-add="soil_add"]').length;
out.soilRemoveButtons = spec().querySelectorAll('[data-spec-remove="soil_add"]').length;
spec().querySelector('[data-spec-add="chute_add"]').click();
@@ -239,9 +239,9 @@
sec()
.querySelectorAll("button")
.forEach((b) => flat(b) === "리셋" && b.click());
// 다단 벽(추가 기슭막이 N단) — 관 유출 벽에 한 단 더함(세션만 · 단이 설 자리가 있는 관까지 차례로)
// 다단 벽(추가 흙막이 N단) — 관 유출 벽에 한 단 더함(세션만 · 단이 설 자리가 있는 관까지 차례로)
out.tier = null;
for (const card of cards().filter((c) => /유출 기슭막이 선택/.test(c.innerHTML))) {
for (const card of cards().filter((c) => /유출 흙막이 선택/.test(c.innerHTML))) {
if (/추가\(성토부\)/.test(card.innerHTML)) {
out.tier = card.id;
break;
@@ -306,7 +306,7 @@
}
// ── 다. 빈 곳 — 구조물 카드 · 빈 카드 · 다시 누름 = 카드 풂 ──
await clearCard();
const pipeCard = targets.find((c) => /유입 집수정|유출 기슭막이/.test(c.innerHTML));
const pipeCard = targets.find((c) => /유입 집수정|유출 흙막이/.test(c.innerHTML));
if (pipeCard) {
click(pipeCard.querySelector("svg"));
await w(1400);
@@ -423,12 +423,12 @@
judge(`값 되돌림: ${key}`, noSpan(back) === noSpan(before), state());
};
// 형태 칸은 구조를 고른 관에서만 섬 — 그 칸이 서는 관을 찾음
for (const c of targets.filter((c) => /유출 기슭막이 선택/.test(c.innerHTML))) {
for (const c of targets.filter((c) => /유출 흙막이 선택/.test(c.innerHTML))) {
await clearCard();
click(uniqueHits(document.getElementById(c.id))[0]);
await w(1400);
if (!ctlOf("outlet.form")) continue;
await bodyCase(c, "outlet.form", "콘크리트", (t) => /유출 기슭막이 콘크리트/.test(t));
await bodyCase(c, "outlet.form", "콘크리트", (t) => /유출 흙막이 콘크리트/.test(t));
break;
}
const fordCard = targets.find((c) => /련/.test(titles(c).join("|")));
@@ -1,10 +1,10 @@
/* 54-10 — B06 다단 흙막이 ↔ 배관 「흙막이」 컨테이너
* 시작 화면: B06(#/b06-section) · 검증 프로젝트(sub5_검증) · 목록에 다단이 서는 관(기본 「배관 2」 12+18)
* 부르는 법: orca eval --expression "window.__m5410={mode:'단계', n:0, item:'', value:''}; $(cat 이 파일)"
* look → 흙막이 컨테이너 수 · 켜진 상자 · 옛 「추가 기슭막이」 칸 · 다단 벽 그림(형태 · 높이)
* look → 흙막이 컨테이너 수 · 켜진 상자 · 옛 「추가 흙막이」 칸 · 다단 벽 그림(형태 · 높이)
* wall n → 횡단도 다단 n 번째(0 부터) 벽을 누름 → look
* set n item value → 흙막이 n+1 컨테이너 item(form · height · length · before · after) 값 바꿈 → look
* tier+ / tier- → 유출 「추가 기슭막이(단)」 + / -
* tier+ / tier- → 유출 「추가 흙막이(단)」 + / -
* 기대: wall n 뒤 켜진 상자 = n · 옛 칸 0 · set form → 벽 형태 · set height → 벽 H(한도 안)
*/
(async () => {
@@ -71,7 +71,7 @@
),
),
extraGroupShown: [...document.querySelectorAll("legend")].filter(
(l) => visible(l) && l.textContent.startsWith("추가 기슭막이"),
(l) => visible(l) && l.textContent.startsWith("추가 흙막이"),
).length,
tier: tierRow()?.querySelector(".b06-structure-panel__hval")?.textContent ?? null,
walls: extraShapes(),
@@ -3,7 +3,7 @@
* 부르는 법: orca eval --expression "window.__m5411={mode:'단계'}; $(cat 이 파일)"
* look → 단 수 · 보이는 흙막이 상자 · 파일의 흙막이 값 번호 · 집계표 줄 수 · 미지정 알림
* prep → 보이는 흙막이 상자마다 구조물 조합 잇기 · 마지막 상자 형태 = form → [저장] → look
* tier+ / tier- → 유출 「추가 기슭막이(단)」 + / - → [저장] → look
* tier+ / tier- → 유출 「추가 흙막이(단)」 + / - → [저장] → look
* 기대: 단 수 2 → 1 에 상자 2 → 1 · 집계 줄(또는 미지정 건) 1 줄어듦 · 파일 값 그대로 ·
* 다시 2 에 줄 · 상자 값 되살아남
*/
@@ -108,10 +108,10 @@
["inlet", "유입"],
["outlet", "유출"],
]) {
const wall = pic.walls.find((t) => t.startsWith(`${role} 기슭막이`));
const wall = pic.walls.find((t) => t.startsWith(`${role} 흙막이`));
if (side === "inlet" && basin) continue;
const f = head0(fields[`${side}.form`]);
if (!wall || flat(wall) !== flat(`${role} 기슭막이 ${f}`))
if (!wall || flat(wall) !== flat(`${role} 흙막이 ${f}`))
bad(`${head}: ${side} 형태 ${f} ↔ ${wall}`);
}
const d = Math.round((set?.culvert?.diameter_m ?? 0) * 1000);
@@ -1,11 +1,11 @@
/* 54-1 — B05 · B06 좌측 패널 배관: 「선택」 글 · 지난 정의 값 숨김 · 관 길이 보여 주는 칸(정수) ·
* 유입 집수정 = 길이 · 전 · 후 숨김 · 유출 읽기 줄(관길이 · 좌우 자동 자리) 없음 · [흙막이 추가] 없음 ·
* 흙막이 컨테이너 수 = 유출 「추가 기슭막이(단)」 N
* 흙막이 컨테이너 수 = 유출 「추가 흙막이(단)」 N
* 시작 화면: B05(#/b05-profile) 또는 B06(#/b06-section) · 검증 프로젝트(sub5_검증) · 목록에 「배관 1」(4+5)
* 부르는 법: orca eval --expression "window.__m541='단계'; $(cat 이 파일)"
* look → 지금 화면 배관 폼 수치(아무것도 안 바꿈)
* basin → 유입 구조 집수정 · 흙막이 번갈아 보이는 칸(되돌림 · 저장 안 함)
* tier+ / tier- → B06 유출 벽을 골라 「추가 기슭막이(단)」 + / - 한 번 · 흙막이 컨테이너 수
* tier+ / tier- → B06 유출 벽을 골라 「추가 흙막이(단)」 + / - 한 번 · 흙막이 컨테이너 수
* 기대: pick 글 전부 「선택」 · 고르기 0 · stale 보임 0 · 관 길이 input 0 · 값 정수 ·
* 집수정 칸 = structure · form · height · 흙막이 칸 = + length · before · after ·
* soil_add 추가 단추 0 · 유출 읽기 줄 0 · soil_add 수 = 단 수
@@ -86,7 +86,7 @@
outletText: box("outlet")?.textContent.includes("자동 자리"),
tier: tierRow()?.querySelector(".b06-structure-panel__hval")?.textContent ?? null,
extraGroupShown: [...document.querySelectorAll("legend")].filter(
(l) => visible(l) && l.textContent.startsWith("추가 기슭막이"),
(l) => visible(l) && l.textContent.startsWith("추가 흙막이"),
).length,
};
};
@@ -120,7 +120,7 @@
for (const form of forms) {
await set(side, "form", form);
const poly = [...card().querySelectorAll(".b06-chart__culvert-revet")].find((p) =>
(p.querySelector("title")?.textContent ?? "").startsWith(`${word} 기슭막이`),
(p.querySelector("title")?.textContent ?? "").startsWith(`${word} 흙막이`),
);
const hatch = poly.nextElementSibling?.nextElementSibling;
rows.push({
+11 -11
View File
@@ -242,16 +242,16 @@ export const ui_locales_b2 = {
B06_Cross_Width_Inc: ["표시 반폭 1m 늘리기", "Widen view width by 1m"],
B06_Cross_Width_Reset: ["전역 반폭으로 초기화", "Reset to global width"],
B06_Cross_Revet_Select: [
"기슭막이 선택 — 화살표로 자리 조절",
"흙막이 선택 — 화살표로 자리 조절",
"Select revetment — nudge with arrows",
],
B06_Cross_Revet_Label: ["기슭막이", "Revetment"],
B06_Cross_Revet_Label: ["흙막이", "Revetment"],
B06_Cross_Revet_Close: ["조정창 닫기", "Close adjuster"],
B06_Cross_Revet_Left: ["왼쪽으로 — 관 길이 1m 단위", "Move left — 1m of pipe length"],
B06_Cross_Revet_Right: ["오른쪽으로 — 관 길이 1m 단위", "Move right — 1m of pipe length"],
B06_Cross_Revet_Reset: ["기슭막이 자동 자리로 초기화", "Reset revetment to solved position"],
B06_Cross_Revet_Inlet: ["기슭막이(유입)", "Revetment (inlet)"],
B06_Cross_Revet_Outlet: ["기슭막이(유출)", "Revetment (outlet)"],
B06_Cross_Revet_Reset: ["흙막이 자동 자리로 초기화", "Reset revetment to solved position"],
B06_Cross_Revet_Inlet: ["흙막이(유입)", "Revetment (inlet)"],
B06_Cross_Revet_Outlet: ["흙막이(유출)", "Revetment (outlet)"],
/* 배관과 무관한 독립 기슭막이(구조물 정본 D군) — 2026-08-28. */
B06_Cross_Revet_Own: ["기슭막이(독립)", "Revetment (standalone)"],
/* 세월교 · BOX암거 유입 · 유출 흙막이 조정창(79b). */
@@ -259,7 +259,7 @@ export const ui_locales_b2 = {
B06_Cross_Guard_Outlet: ["흙막이(유출)", "Retaining wall (outlet)"],
B06_Cross_Struct_Label: ["구조물 형식", "Structure type"],
// 조정창 제목은 "기슭막이(…)" 한 형식으로 통일한다(2026-08-29 사용자). {n} = 단 번호.
B06_Cross_Revet_Extra: ["기슭막이(추가 {n})", "Revetment (extra {n})"],
B06_Cross_Revet_Extra: ["흙막이(추가 {n})", "Revetment (extra {n})"],
B06_Cross_Revet_Up: ["사면 위로(대각) 1m", "Up along fill slope 1m"],
B06_Cross_Revet_Down: ["사면 아래로(대각) 1m", "Down along fill slope 1m"],
B06_Cross_Revet_Limit_Up: [
@@ -283,7 +283,7 @@ export const ui_locales_b2 = {
B06_Cross_Mat_Dry: ["메쌓기", "Dry masonry"],
B06_Cross_Mat_Wet: ["찰쌓기", "Wet masonry"],
B06_Cross_Mat_Concrete: ["콘크리트", "Concrete"],
B06_Cross_Extra_Count: ["추가 기슭막이(단)", "Extra revetments"],
B06_Cross_Extra_Count: ["추가 흙막이(단)", "Extra revetments"],
B06_Cross_Extra_Add: ["한 단 추가", "Add one tier"],
B06_Cross_Extra_Equalize: [
"다단 등간격 배치 — 사면 구간을 같게",
@@ -291,17 +291,17 @@ export const ui_locales_b2 = {
],
B06_Cross_Extra_Remove: ["한 단 삭제", "Remove one tier"],
B06_Cross_Struct_Auto: ["자동(규칙)", "Auto (by rule)"],
B06_Cross_Struct_Revet: ["기슭막이+배관", "Revetment + pipe"],
B06_Cross_Struct_Revet: ["흙막이+배관", "Revetment + pipe"],
B06_Cross_Struct_I: ["집수정 I형", "Catch basin — I"],
B06_Cross_Struct_L: ["집수정 ㄴ형", "Catch basin — L"],
B06_Cross_Struct_U: ["집수정 ㄷ형", "Catch basin — U"],
B06_Cross_Struct_InletBasin: ["유입 집수정", "Inlet catch basin"],
B06_Cross_Revet_Limit_Inward: [
"기슭막이 안쪽 한계 — 노견(도로) 안쪽으로는 들어갈 수 없습니다.",
"흙막이 안쪽 한계 — 노견(도로) 안쪽으로는 들어갈 수 없습니다.",
"Revetment inner limit — cannot move inside the road shoulder.",
],
B06_Cross_Revet_Limit_Outward: [
"기슭막이 바깥 한계 — 조정 범위를 벗어나 더 밀 수 없습니다.",
"흙막이 바깥 한계 — 조정 범위를 벗어나 더 밀 수 없습니다.",
"Revetment outer limit — cannot push beyond the adjustment range.",
],
B06_Profile_View_Longitudinal: ["종단면도", "Longitudinal profile"],
@@ -345,7 +345,7 @@ export const ui_locales_b2 = {
/* 성토사면 경사길이 — 구조물이 사면을 끊기 전 **본래** 길이(2026-09-03 사용자 지시). */
B06_Cross_FillSlope: ["성토사면", "Fill slope"],
B06_Cross_FillSlope_Tip: [
"노견 끝(그 측에 측구가 있으면 측구 바깥)부터 성토 사면이 원지반과 만나는 곳까지의 경사길이입니다. 기슭막이·집수정이 사면을 끊기 전 본래 길이이며, 양측 성토면 좌·우를 함께 적습니다. 5m를 넘으면 옹벽·석축 등이 필요합니다(성토_비탈면 §2). 「≥」는 사면이 계산 반폭 안에서 원지반과 만나지 못해 거기까지만 잰 하한값입니다.",
"노견 끝(그 측에 측구가 있으면 측구 바깥)부터 성토 사면이 원지반과 만나는 곳까지의 경사길이입니다. 흙막이·집수정이 사면을 끊기 전 본래 길이이며, 양측 성토면 좌·우를 함께 적습니다. 5m를 넘으면 옹벽·석축 등이 필요합니다(성토_비탈면 §2). 「≥」는 사면이 계산 반폭 안에서 원지반과 만나지 못해 거기까지만 잰 하한값입니다.",
"Slant length of the fill slope, from the shoulder edge (outside the ditch if that side has one) to where it meets existing ground. It is the original slope before any revetment or catch basin cuts it short, and both sides are shown when the section is filled on both. Over 5 m a retaining structure is required. A leading ≥ marks a lower bound: the slope never meets ground within the computed half-width, so only that much could be measured.",
],
B06_MassHaul_CutNatural: ["절토(자연)", "Cut (natural)"],