Files
Aislo/B05_Profile/B05_Profile_UI_Style.css
T
eomsangdonandClaude Opus 5 66717162c5 feat(B05): 도구줄 [초기화] 추가 + 쉬프트 선택 구간 빨간 강조
1. [초기화] — [↶] 왼쪽에 둠. 전체 초기화가 아니라 **마지막 [저장] 시점**이 기준점임
   (사용자 지시). 편집 스토어가 저장 시점 편집분(`savedBaseline`)을 들고 있다가 그리로
   되돌림 — 저장한 작업은 남고 그 뒤 편집만 버림. 이력에 기록해 [↶]로 되살릴 수 있음.
   되돌릴 것이 없으면 버튼은 비활성.
   자동 선형까지 지우는 `resetAll`(좌측 [초기화])과는 다른 조작임.

2. [쉬프트]로 고른 직선이 화면에 표시되지 않아 무엇이 잡혔는지 알 수 없었음. 고른 구간을
   빨갛게 덧그림 — 범위는 **직선 + 양 끝 라운드(R)** 임. 쉬프트는 직선을 통째로 올리고
   내리므로 양 끝 호의 모양까지 함께 바뀜. 구간 시작 라운드의 BVC부터 끝 라운드의 EVC
   까지 계획선 샘플을 그대로 덧그려 곡률이 따라옴. 경계에는 점선 세로 표식.

   공용 종단 렌더러(`B06_Section_UI_Longitudinal`)는 건드리지 않고 별도 오버레이로 얹음
   (`B05_Profile_UI_Profile_RunHighlight.ts`). Y 매핑은 그 렌더러가 넘겨 주는 축 눈금에서
   되짚어 같은 스케일을 씀. 편집 버튼층(z-index 4) 아래(3)라 버튼을 가리지 않음.

`tsc --noEmit` 통과, 전체 테스트 183건 통과.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 20:42:23 +09:00

1344 lines
39 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.b05-route-layout {
height: calc(100vh - var(--spacing-64));
height: calc(100dvh - var(--spacing-64));
min-height: 0;
overflow: hidden;
}
.b05-route-layout .ui-workflow-layout__body,
.b05-route-layout .ui-workflow-layout__main {
height: 100%;
min-height: 0;
}
.b05-route-layout .ui-workflow-layout__main {
overflow: hidden;
}
.b05-route__main {
position: relative;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 0;
overflow: hidden;
}
.b05-route__viewport {
position: relative;
width: 100%;
flex: 1 1 auto;
height: auto;
min-height: 0;
overflow: hidden;
background: var(--color-surface);
}
/* 하단 종단 패널: 그래프 + 12행 도면 테이블을 담기 위해 화면 높이의 60%를 차지한다.
3D 뷰포트를 밀어내지 않고 그 위를 덮는 오버레이로 띄운다 — 패널을 여닫아도 3D 뷰
크기가 변하지 않아 카메라 시점과 렌더 비용이 그대로 유지된다.
접기 핸들로 언제든 내릴 수 있어 3D 뷰를 전체 영역으로 볼 수 있다. */
.b05-route-profile {
position: absolute;
z-index: 3;
right: 0;
bottom: 0;
left: 0;
display: flex;
height: 60vh;
/* 기본은 화면의 60%. 위쪽 경계를 끌면 --b05-profile-height(px)가 대신 들어온다. */
height: var(--b05-profile-height, 60dvh);
flex-direction: column;
min-height: 0;
overflow: visible;
border-top: 1px solid var(--color-border);
background: var(--color-surface-raised);
transition: height var(--transition-fast);
}
/* 끄는 동안에는 transition을 끈다 — 켜 두면 손보다 패널이 늦게 따라온다. */
.b05-route-profile.is-resizing,
.b05-drainage.is-resizing {
transition: none;
}
/* 접힌 패널에는 끌 것이 없다 — 손잡이를 숨겨 잘못 잡히지 않게 한다. */
.b05-route-profile.is-collapsed > .ui-resizer,
.b05-drainage.is-collapsed > .ui-resizer {
display: none;
}
.b05-route-profile.is-collapsed {
height: 0;
min-height: 0;
}
/* 종단면 본문 + 우측 배수유역 패널의 2단 가로 배치. */
.b05-route-profile__content {
display: flex;
flex: 1 1 auto;
min-height: 0;
min-width: 0;
}
/* 그래프 본문 + 로딩 서클을 겹치기 위한 칸. body는 그릴 때마다 자식이 교체된다. */
.b05-route-profile__body-wrap {
position: relative;
display: flex;
flex: 1 1 auto;
min-width: 0;
min-height: 0;
}
.b05-route-profile__body {
box-sizing: border-box;
flex: 1 1 auto;
min-width: 0;
min-height: 0;
overflow-x: scroll;
overflow-y: hidden;
/* 좌측 여백을 0으로 둔다. 여백이 있으면 sticky 이름표 열이 그 여백까지 못 덮어,
가로 스크롤 시 셀 값이 이름표 왼쪽 틈으로 새어 보인다. 우측만 숨 돌릴 여백을 준다. */
padding-left: 0;
padding-right: 15px;
}
/* 가로 스크롤바는 전역 pill 스크롤바(테마 `*::-webkit-scrollbar`, 8px)를 그대로 쓴다 —
좌측 사이드바와 동일한 형태로 통일. (예전엔 이 패널만 12px 두꺼운 막대로 덮어썼다.) */
/* 그래프와 테이블을 같은 폭으로 쌓아 X축이 저절로 맞물리게 한다. */
.b05-profile__canvas {
position: relative; /* 유토곡선 경계 드래그 바(absolute)의 기준 */
display: flex;
flex-direction: column;
}
.b05-profile__chart {
position: relative;
flex: 0 0 auto;
min-height: 0;
}
/* 가로 스크롤 고정 Y축(.b05-profile__yaxis*)의 정의처는
B05_Profile_UI_Style_MassHaul.css다 — buildStickyYAxis를 쓰는 B05·B06이 함께 얻는다. */
.b05-route-profile.is-collapsed .b05-route-profile__body-wrap,
.b05-route-profile.is-collapsed .b05-route-profile__body,
.b05-route-profile.is-collapsed .b05-route-profile__balance {
display: none;
}
.b05-route-profile__empty {
margin: 0;
padding: var(--spacing-24);
color: var(--color-text-muted);
font-size: var(--text-body-sm);
}
.b05-route-profile .b06-section__chart-wrap {
height: 100%;
}
/* X·Y축 제목은 아래 도면 테이블이 그대로 담고 있어 중복이라 숨긴다. */
.b05-route-profile .b06-chart__axis-label {
display: none;
}
/* 측점 이름(세로선 항목)은 그래프에서도 보여야 어느 측점의 선인지 바로 읽힌다.
다만 하단 편집 버튼(is-down: bottom 2px, 높이 15px)과 겹치지 않도록 라벨을 살짝
위로 올려 하단 여백 위쪽에 앉힌다(하단 버튼 밴드와 분리). */
.b05-route-profile .b06-chart__station-label {
transform: translateY(-9px);
}
/* 비정규 측점(구조물)은 그래프에서도 파선 세로선으로 구분해 규칙 측점과 헷갈리지 않게 한다. */
.b05-route-profile .b06-chart__station-line--irregular {
stroke: var(--color-royal-amethyst, rgb(139 92 246));
stroke-width: 1.4;
stroke-dasharray: 5 3;
}
.b05-route__viewport canvas {
display: block;
width: 100%;
height: 100%;
}
/* 3D 뷰포트 안내 문구 — 우상단 텍스트만(배경·테두리 없음, 회색). 2026-08-01 사용자 지시.
우측 세로 진행단계 오버레이와 겹치지 않도록 그 폭만큼 안쪽으로 들여 놓는다. */
.b05-route__viewer-status {
position: absolute;
z-index: 2;
top: var(--spacing-16);
right: calc(var(--wf-left-panel-width) / 2 + var(--spacing-48));
left: auto;
max-width: 420px;
color: var(--color-text-muted);
font-size: var(--text-caption);
text-align: right;
pointer-events: none;
}
/* 뷰셋·표시 토글 버튼 묶음 — 좌상단(2026-08-01 사용자 지시).
안내 문구가 우상단으로 옮겨져 좌상단이 비었다. */
.b05-route__view-controls {
position: absolute;
z-index: 2;
top: var(--spacing-16);
left: var(--spacing-16);
display: flex;
align-items: center;
gap: var(--spacing-8);
}
/* 방위 나침반을 놓을 자리 — ISO 버튼 바로 아래(2026-09-03 사용자 지시).
버튼 줄 높이 34px + 위 여백 16px 아래에 붙인다. 위젯 모양·색은 공용
(`ui_template/ui_template_compass.css`)이고 여기서는 위치만 준다. */
.b05-route__compass {
top: calc(var(--spacing-16) + 34px + var(--spacing-8));
left: var(--spacing-16);
}
.b05-route__view-group {
display: flex;
gap: var(--spacing-4);
}
.b05-route__view-controls .ui-btn {
height: 34px;
padding: 0 var(--spacing-8);
}
.b05-route__view-separator {
width: 1px;
height: 24px;
background: color-mix(in srgb, var(--color-border) 75%, transparent);
}
/* [3D 업데이트] 대기 표시 — 계획선 편집이 3D에 아직 안 들어갔음을 점으로 알린다. */
.b05-route__view-controls .ui-btn.is-pending {
position: relative;
border-color: var(--color-warning, #d98a00);
}
.b05-route__view-controls .ui-btn.is-pending::after {
content: "";
position: absolute;
top: 4px;
right: 4px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--color-warning, #d98a00);
}
.b05-route__panel {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
gap: var(--spacing-16);
}
/* 스크롤 영역 상단 여유 — 첫 컨테이너(구조물 배치)가 패널 상단 경계에 붙어
답답하다는 보고(2026-08-18 사용자 지시). */
.b05-route__panel .ui-sidebar-scroll {
padding-top: var(--spacing-8);
/* 하단 여유 패딩은 두지 않는다 — 액션 dock이 최하단에 붙는 지금은 버튼 아래
빈 띠로만 남는다(2026-08-18). 공용 body 패딩(16px)은 dock의 sticky
bottom:-16px가 덮는다. */
}
.b05-route__panel-section {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
gap: var(--spacing-8);
padding: calc(var(--spacing-8) + var(--spacing-4));
/* 테두리는 공통 ui-sidebar-section이 전담 — 로컬 border 선언이 공통 색을 덮던
문제 제거(2026-08-06 사용자 보고). */
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
}
/* 제목 행 클릭 접기/펼치기는 공용 collapsible(ui_template_theme.css)에서 처리한다. */
/* 사용여부 미결정 컨테이너(경로 계산 설정) — 숨기지 않고 "조작 안 되는 항목"임이
보이게 글자·배경을 가라앉힌다(2026-08-18 사용자 지시). 입력 차단은 본문 inert가
맡고, 여기서는 시각만 담당한다. 제목(접기)은 살아 있으므로 흐림에서 제외하지
않는다 — 컨테이너 전체가 한 덩어리로 비활성으로 읽혀야 한다. */
.b05-route__panel-section.is-disabled {
background: color-mix(in srgb, var(--color-surface-raised) 60%, var(--color-canvas));
}
.b05-route__panel-section.is-disabled > * {
opacity: 0.45;
}
.b05-route__panel-section h3 {
margin: 0;
color: var(--color-text);
font-size: var(--text-body-sm);
}
.b05-route__panel-body,
.b05-route__field,
.b05-route__metrics {
display: flex;
flex-direction: column;
min-width: 0;
gap: var(--spacing-8);
}
.b05-route__contour-row {
display: flex;
align-items: end;
gap: var(--spacing-8);
}
/* 2열 그리드 — 각 열은 항목명(위)+값 입력(아래). '공사 시작점' 등 나란한 입력에 쓴다. */
.b05-route__field-row {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--spacing-8);
}
.b05-route__contour-row .b05-route__field {
flex: 1;
}
.b05-route__contour-row .ui-btn {
flex: 0 0 auto;
}
.b05-route__field,
.b05-route__check,
.b05-route__metrics {
color: var(--color-text-body);
font-size: var(--text-caption);
}
.b05-route__field input,
.b05-route__field select,
.b05-route__panel-section > select,
.b05-route__textarea {
width: 100%;
min-width: 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);
}
/* ─── 비정규 측점(구조물) 섹션 ─────────────────────────────────────────── */
.b05-route__irregular-row {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: var(--spacing-8);
}
.b05-route__irregular-actions {
display: flex;
gap: var(--spacing-8);
}
.b05-route__irregular-btn {
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 {
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent);
color: var(--color-text);
}
.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 {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
margin: 0;
padding: 0;
list-style: none;
/* 목록이 5개를 넘으면 사이드 패널을 밀어내지 않고 내부 스크롤로 돈다(2026-08-04 사용자
지시). 상한 = 항목 5개 높이(항목 약 30px + gap 4px) — 5개까지는 현행과 동일하게 다
보이고 6개째부터 스크롤이 생긴다. */
max-height: 170px;
overflow-y: auto;
}
.b05-route__irregular-item {
display: flex;
align-items: baseline;
gap: var(--spacing-8);
padding: var(--spacing-4) var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface);
cursor: pointer;
}
.b05-route__irregular-item.is-selected {
border-color: var(--color-royal-amethyst, rgb(109 40 217));
background: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 12%, transparent);
}
.b05-route__irregular-item strong {
color: var(--color-text);
font-variant-numeric: tabular-nums;
}
/* 항목 = [측점(좌측)][구조물 이름(우측)] — 이름이 주 정보라 본문색, 남는 폭은
왼쪽 마진이 먹어 오른쪽 끝에 붙는다(2026-08-18 사용자 지시). */
.b05-route__irregular-item span {
overflow: hidden;
margin-left: auto;
color: var(--color-text);
font-size: var(--text-caption);
text-align: right;
text-overflow: ellipsis;
white-space: nowrap;
}
.b05-route__irregular-empty {
padding: var(--spacing-4) 0;
color: var(--color-text-muted);
font-size: var(--text-caption);
list-style: none;
}
.b05-route__check {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
.b05-route__button-grid,
.b05-route__palette {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
min-width: 0;
gap: var(--spacing-8);
}
.b05-route__chip {
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__chip.is-ep,
.b05-route__chip.is-fp {
color: var(--color-danger);
}
.b05-route__chip.is-bp {
color: var(--color-success);
}
.b05-route__chip.is-cp,
.b05-route__stale {
color: var(--color-warning);
}
/* 사이드 최하단 고정 영역 — 공용 ui-sidebar-actions(sticky·배경·상단 구분선)를 그대로
쓰되, 안쪽은 [구조물 목록][중간 구분선][액션 버튼 행] 세로 배치로 되돌린다
(2026-08-18 사용자 지시). */
.b05-route__dock.ui-sidebar-actions {
flex-direction: column;
}
/* 공용 규칙(.ui-sidebar-actions > * { flex: 1 })이 목록·구분선까지 늘리지 않도록
같은 자리에서 되돌린다 — 로드 순서에 기대지 않게 선택자를 한 단계 더 좁혔다. */
.b05-route__dock.ui-sidebar-actions > * {
flex: none;
}
.b05-route__actions {
display: flex;
gap: var(--spacing-8);
}
.b05-route__actions > * {
flex: 1;
}
.b05-route__stale {
font-size: var(--text-caption);
font-weight: var(--font-weight-medium);
}
.b05-route__panel details {
color: var(--color-text-body);
font-size: var(--text-caption);
}
/* 계획선 설계 폼 안내문 */
.b05-route__note {
margin: 0;
color: var(--color-text-muted, var(--color-plum-velvet));
font-size: var(--text-caption);
line-height: 1.4;
}
/* 종단면도 상단 절·성토 균형 지표 바 */
.b05-route-profile__balance {
display: flex;
flex: 0 0 auto;
flex-wrap: nowrap;
gap: var(--spacing-16);
align-items: center;
height: 24px;
padding: 0 var(--spacing-8);
overflow-x: auto;
overflow-y: hidden;
font-size: var(--text-caption);
white-space: nowrap;
}
.b05-route-profile__balance-item {
display: inline-flex;
gap: var(--spacing-8);
align-items: center;
}
.b05-route-profile__balance-item em {
color: var(--color-text-muted, var(--color-plum-velvet));
font-style: normal;
}
.b05-route-profile__balance-item.is-cut em {
color: rgb(220 38 38);
}
.b05-route-profile__balance-item.is-fill em {
color: rgb(37 99 235);
}
.b05-route-profile__balance-item.is-over,
.b05-route-profile__balance-item.is-unsaved {
color: rgb(180 83 9);
font-weight: var(--font-weight-medium);
}
.b05-route-profile__balance-item.is-edited em {
color: var(--color-royal-amethyst, rgb(109 40 217));
}
.b05-route-profile__balance-warning {
overflow: hidden;
color: rgb(180 83 9);
text-overflow: ellipsis;
}
/* 도구줄 — 요약줄 맨 앞(최대 기울기 칩 왼쪽). 버튼 크기는 그래프 위 틸팅 버튼과 같다
(21×17px, 2026-09-02 사용자 지시). */
.b05-route-profile__tools {
display: inline-flex;
flex: 0 0 auto;
align-items: center;
gap: 2px;
margin-right: var(--spacing-8);
}
.b05-route-profile__tool {
height: 17px;
min-width: 21px;
padding: 0 3px;
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;
}
.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 {
color: var(--color-text-muted);
font-size: var(--text-caption);
font-style: normal;
}
/* ─── 도면 테이블 (구배 ~ 곡선 9행) ───────────────────────────────────────
셀은 종단면도와 같은 X 매핑으로 절대 배치되어 측점 수직선과 맞물린다.
행 이름표만 sticky로 좌측에 고정되어 가로 스크롤에도 계속 보인다. */
.b05-profile-table {
position: relative;
display: flex;
flex: 0 0 auto;
flex-direction: column;
min-height: 0;
border-top: 2px solid var(--color-text-muted, var(--color-plum-velvet));
color: var(--color-text-body);
/* 행 높이와 셀 폭에 맞춰 렌더러가 계산해 넣는다 (createProfileTable). */
font-size: var(--b05-table-font, 11px);
line-height: 1.1;
user-select: none;
}
/* 12개 행이 테이블 세로를 균등하게 나눠 갖는다 (패널을 키우면 행이 두꺼워진다). */
.b05-profile-table__row {
position: relative;
flex: 1 1 0;
min-height: 12px;
border-bottom: 1px solid var(--color-border);
white-space: nowrap;
}
/* 구배 / 측점값 / 곡선 묶음의 시작 행은 굵은 선으로 그룹을 구분한다. */
.b05-profile-table__row.is-group-start {
border-top: 2px solid var(--color-text-muted, var(--color-plum-velvet));
}
.b05-profile-table__row--grade {
background: color-mix(in srgb, var(--color-surface) 55%, transparent);
}
.b05-profile-table__row:last-child {
border-bottom: 0;
}
/* 가로 스크롤 시 값들이 이름표 열을 뚫고 보이지 않도록 가장 위 층에 불투명하게 둔다. */
.b05-profile-table__label {
position: sticky;
z-index: 5;
left: 0;
display: inline-flex;
align-items: center;
/* 행제목이 좌측 편집 버튼 층에 걸린다 — 글자를 우측으로 맞추고 좌측 여백을 더 민다
(2026-08-05 사용자 지시). */
justify-content: flex-end;
box-sizing: border-box;
width: var(--b05-table-label-width, 60px);
height: 100%;
padding-inline: var(--spacing-16) var(--spacing-4);
border-right: 2px solid var(--color-text-muted, var(--color-plum-velvet));
background: var(--color-surface-raised);
color: var(--color-text);
font-size: var(--b05-table-label-font, inherit);
font-weight: var(--font-weight-medium);
white-space: nowrap;
}
.b05-profile-table__cell,
.b05-profile-table__segment,
.b05-profile-table__curve {
position: absolute;
top: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
overflow: hidden;
}
/* 이웃 곡선 셀과 겹치는 좁은 자리(배관 측점 등) — 셀을 얇게 줄이고 입력을 세로쓰기로
세운다. 자리는 측점 수직선 그대로다(옆으로 비키면 다른 열 값으로 오해, 2026-08-04). */
.b05-profile-table__curve.is-rotated input {
writing-mode: vertical-rl;
width: 100%;
height: 100%;
padding: 0;
text-align: center;
}
/* 측점 값 셀은 측점 수직선을 중심으로 좌우 대칭 배치된다.
셀 경계(= 이웃 측점과의 중간)에 세로 구분선을 둬 구배 행과 같은 격자를 만든다. */
.b05-profile-table__cell,
.b05-profile-table__curve {
box-sizing: border-box;
width: var(--b05-table-cell-width, 56px);
border-left: 1px solid var(--color-border);
transform: translateX(-50%);
}
.b05-profile-table__cell:last-child,
.b05-profile-table__curve:last-child {
border-right: 1px solid var(--color-border);
}
/* 구조물 측점 빈 칸의 세로선은 이웃 규칙 측점의 값 한가운데를 지나고, 곡선 L/R 칸
경계와 x가 겹쳐 곡선 행 구분선이 위 행들까지 이어진 것처럼 보인다. 곡선 행에만
남기고 값 행에서는 지운다(2026-08-30 사용자 지시). */
.b05-profile-table__cell.is-structure {
border-left-color: transparent;
}
.b05-profile-table__row.is-cut .b05-profile-table__cell {
color: rgb(220 38 38);
}
.b05-profile-table__row.is-fill .b05-profile-table__cell {
color: rgb(37 99 235);
}
.b05-profile-table__row.is-plan .b05-profile-table__cell {
color: var(--color-royal-amethyst, rgb(109 40 217));
font-weight: var(--font-weight-medium);
}
/* 구간 블록: 변화점 사이를 빈틈없이 채운다.
구분선은 변화점(= 생성된 R의 중심, 측점 수직선) 위에 놓이므로 왼쪽 테두리 하나면
충분하다. 양쪽에 다 주면 맞닿는 자리가 2px로 두꺼워진다. */
.b05-profile-table__segment {
box-sizing: border-box;
border-left: 1px solid var(--color-border);
}
.b05-profile-table__segment:last-child {
border-right: 1px solid var(--color-border);
}
.b05-profile-table__segment.is-violation {
background: color-mix(in srgb, rgb(220 38 38) 12%, transparent);
color: rgb(180 83 9);
}
/* 구배 블록 값. 가로로 안 들어가면 90도 회전해 좁은 블록에도 값을 표기한다. */
.b05-profile-table__segment-value {
line-height: 1;
white-space: nowrap;
}
.b05-profile-table__segment-value.is-rotated {
transform: rotate(-90deg);
}
.b05-profile-table__curve {
z-index: 2;
}
.b05-profile-table__curve.is-optional {
opacity: 0.75;
}
.b05-profile-table__curve.is-omitted {
text-decoration: line-through;
}
.b05-profile-table__radius {
box-sizing: border-box;
width: 100%;
height: 90%;
padding: 0 1px;
border: 1px solid var(--color-border);
border-radius: 2px;
background: var(--color-surface);
color: var(--color-text-body);
font-size: inherit;
text-align: center;
}
/* 숫자 입력의 상·하 토글(스피너) 제거 — 항상 사용자가 값 직접 입력. */
.b05-profile-table__no-spin::-webkit-outer-spin-button,
.b05-profile-table__no-spin::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
appearance: none;
}
.b05-profile-table__no-spin {
-moz-appearance: textfield;
appearance: textfield;
}
/* ─── 선택된 비정규 측점 값 열 오버레이 (규칙 열과 같은 12행) ──────────────
세로 점선·구조물 태그 없이, 선택 시에만 값 열을 테이블 위에 겹쳐 보여준다. */
/* ── 구조물 우클릭 메뉴 (그래프 위 — 배관 추가 · 구조물 삭제) ─────────────── */
.b05-profile-chart__context-menu {
position: absolute;
z-index: 7;
display: flex;
flex-direction: column;
min-width: 120px;
padding: var(--spacing-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: 0 4px 16px rgb(0 0 0 / 25%);
}
.b05-profile-chart__context-menu[hidden] {
display: none;
}
.b05-profile-chart__context-menu-item {
padding: var(--spacing-4) var(--spacing-8);
border: 0;
border-radius: var(--radius-cards);
background: transparent;
color: var(--color-text-body);
font-size: 12px;
text-align: left;
cursor: pointer;
}
.b05-profile-chart__context-menu-item:hover {
background: var(--color-surface-sunken);
}
/* 구조물군 하위 메뉴(아코디언, 2026-08-18 메뉴 일원화) — 머리글 굵게, 종류는 들여쓰기.
종류가 많은 군이 펼쳐지면 메뉴가 화면을 넘을 수 있어 최대 높이에서 안쪽 스크롤. */
.b05-profile-chart__context-menu,
.b05-drainage__context-menu {
max-height: 320px;
overflow-y: auto;
}
.b05-profile-chart__context-menu-item.is-group,
.b05-drainage__context-menu-item.is-group {
font-weight: var(--font-weight-medium);
}
.b05-profile-chart__context-menu-sub,
.b05-drainage__context-menu-sub {
display: flex;
flex-direction: column;
}
.b05-profile-chart__context-menu-item.is-sub,
.b05-drainage__context-menu-item.is-sub {
padding-left: var(--spacing-24);
}
.b05-profile-table__irregular-col {
position: absolute;
/* 값 셀(0)·곡선(2) 위, sticky 행 이름표(5) **아래**로 둔다 — 스크롤로 값 열이 이름표까지 와도
행 제목이 가려지지 않는다. */
z-index: 4;
top: 0;
bottom: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
border-inline: 1px solid var(--color-royal-amethyst, rgb(109 40 217));
background: color-mix(
in srgb,
var(--color-royal-amethyst, rgb(109 40 217)) 14%,
var(--color-surface)
);
transform: translateX(-50%);
/* 열 자체는 클릭을 통과시키고, 입력 셀만 pointer-events를 되살린다(작업6). */
pointer-events: none;
}
/* 비정규(구조물) 측점은 측점 격자와 무관한 위치에 떠서, 이웃 셀을 가린 자리에 뜬다.
떠 있는 창임이 드러나게 그림자·굵은 테두리로 구분한다(규칙 측점은 격자와 일치해 불필요). */
.b05-profile-table__irregular-col.is-floating {
border-inline-width: 2px;
box-shadow:
0 0 0 1px var(--color-surface-raised),
0 4px 12px rgb(0 0 0 / 28%);
}
.b05-profile-table__irregular-col-cell {
display: flex;
flex: 1 1 0;
align-items: center;
justify-content: center;
min-height: 0;
overflow: hidden;
border-bottom: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
color: var(--color-text);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* 변화점에서 좌/우로 갈린 값: 세로 구분선으로 반씩 나누고 각 반쪽은 폰트를 줄여 셀에 맞춘다. */
.b05-profile-table__irregular-col-cell.is-split {
gap: 0;
}
.b05-profile-table__irregular-col-half {
display: flex;
flex: 1 1 0;
align-items: center;
justify-content: center;
min-width: 0;
height: 100%;
overflow: hidden;
font-size: 0.72em;
}
.b05-profile-table__irregular-col-half:first-child {
border-right: 1px solid color-mix(in srgb, var(--color-border) 85%, transparent);
}
.b05-profile-table__irregular-col-cell:last-child {
border-bottom: 0;
}
.b05-profile-table__irregular-col-cell.is-cut {
color: rgb(220 38 38);
}
.b05-profile-table__irregular-col-cell.is-fill {
color: rgb(37 99 235);
}
/* 계획고(is-plan)는 오버레이에서 별도 강조 없이 다른 값과 동일한 스타일을 쓴다(작업 C-2). */
/* 값 열의 계획고 직접 입력 셀 — 열은 pointer-events:none이라 입력만 되살린다. */
.b05-profile-table__irregular-input {
box-sizing: border-box;
width: 92%;
height: 88%;
padding: 0;
/* 계획고만 테두리·색으로 따로 강조하지 않는다 — 같은 열의 다른 값과 같아 보여야 한다
(2026-08-02 사용자 지시). 고칠 수 있다는 표시는 커서와 포커스 테두리로 충분하다. */
border: 1px solid transparent;
border-radius: 2px;
background: transparent;
color: inherit;
font: inherit;
text-align: center;
pointer-events: auto;
}
.b05-profile-table__irregular-input:focus,
.b05-profile-table__irregular-input:hover {
border-color: var(--color-royal-amethyst, rgb(109 40 217));
background: var(--color-surface);
}
/* ─── 계획고 편집 버튼 (크기 유지·상시 표시·밝은 글자) ───────────────────── */
/* [쉬프트]로 고른 직선 구간 강조 — 직선과 **양 끝 라운드**를 함께 빨갛게 덧그린다
(2026-09-03 사용자 지시). 편집 버튼층(z-index 4)보다 아래에 깔아 버튼을 가리지 않는다. */
.b05-profile-runmark {
position: absolute;
z-index: 3;
inset: 0;
pointer-events: none;
}
.b05-profile-runmark__line {
fill: none;
stroke: var(--color-danger, #d64545);
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
opacity: 0.85;
}
/* 구간 경계(라운드 시·종점) 세로 표식 — 어디까지 함께 움직이는지 알린다. */
.b05-profile-runmark__edge {
stroke: var(--color-danger, #d64545);
stroke-width: 1;
stroke-dasharray: 3 3;
opacity: 0.5;
}
.b05-profile-edit {
position: absolute;
z-index: 4;
inset: 0;
pointer-events: none;
}
/* 상시 보이게 둔다(예전엔 패널 hover 시에만 노출). 크기는 ▲▼·↺·⬆⬇ 전부 21×17로
키웠다(2026-08-04 사용자 지시 — 18×15는 누르기도 읽기도 작았다). */
.b05-profile-edit__btn {
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;
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:focus-visible {
border-color: var(--color-border);
background: var(--color-surface-raised);
color: var(--color-text);
opacity: 1;
}
.b05-profile-edit__btn.is-up {
top: 2px;
}
.b05-profile-edit__btn.is-down {
bottom: 2px;
}
/* 구간 시프트 버튼은 측점 버튼과 같은 줄(is-up/is-down)에 놓이고, 겹치는 자리에서만
렌더러가 좌우로 비켜 배치한다. 글리프(⇧⇩ vs ▲▼)로 구분한다. */
/* 방향별 색 — 올림은 빨강, 내림은 파랑(2026-08-04 사용자 지시). 무채색이던 버튼이
배경에 묻혀 잘 안 보였다. --color-danger는 테마별로 정의돼 있고(라이트 #c0392b /
다크 #e8695a), 파랑 #5b8def(--color-chart-0)는 두 테마 모두에서 대비가 나온다.
is-segment(⇧⇩)에도 같은 방향색을 적용한다 — 구분은 글리프가 맡는다. */
.b05-profile-edit__btn.is-up {
border-color: color-mix(in srgb, var(--color-danger) 65%, transparent);
color: var(--color-danger);
}
/* 구간 이동(⬆⬇)은 속 찬 화살표가 잘 읽히도록 굵게(크기는 공통 21×17). */
.b05-profile-edit__btn.is-segment {
font-weight: 700;
}
.b05-profile-edit__btn.is-down {
border-color: color-mix(in srgb, var(--color-chart-0, #5b8def) 65%, transparent);
color: var(--color-chart-0, #5b8def);
}
.b05-route-profile .b05-profile-edit__btn.is-up:hover,
.b05-route-profile .b05-profile-edit__btn.is-up:focus-visible {
border-color: var(--color-danger);
background: color-mix(in srgb, var(--color-danger) 14%, var(--color-surface-raised));
color: var(--color-danger);
}
.b05-route-profile .b05-profile-edit__btn.is-down:hover,
.b05-route-profile .b05-profile-edit__btn.is-down:focus-visible {
border-color: var(--color-chart-0, #5b8def);
background: color-mix(in srgb, var(--color-chart-0, #5b8def) 14%, var(--color-surface-raised));
color: var(--color-chart-0, #5b8def);
}
.b05-profile-edit__btn.is-reset {
top: 22px;
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent);
background: var(--color-surface-raised);
color: color-mix(in srgb, var(--color-royal-amethyst, rgb(139 92 246)) 85%, var(--color-text));
opacity: 0.95;
}
/* ─── 배수유역도 패널 (하단 종단 패널 안쪽 우측 2단 사이드 패널) ──────────── */
.b05-drainage {
position: relative;
display: flex;
/* 기본은 하단 패널의 38%. 왼쪽 경계를 끌면 --b05-drainage-width(px)가 대신 들어온다.
상한 70%는 종단면도가 최소 30%는 남게 하는 안전판(JS 쪽 클램프와 같은 값). */
width: var(--b05-drainage-width, 38%);
min-width: 320px;
max-width: 70%;
flex: 0 0 auto;
flex-direction: column;
min-height: 0;
border-left: 1px solid var(--color-border);
background: var(--color-surface-raised);
transition: width var(--transition-fast);
}
/* 접으면 폭만 0으로 줄고, 좌측 가장자리 핸들은 남아 다시 펼 수 있다. */
.b05-drainage.is-collapsed {
width: 0;
min-width: 0;
}
.b05-drainage.is-collapsed .b05-drainage__header,
.b05-drainage.is-collapsed .b05-drainage__viewport {
display: none;
}
/* 좌측 가장자리 세로 중앙 핸들 — 공용 side 핸들을 패널 왼쪽 밖으로 내보낸다.
유토곡선 오버레이(z-index 5)가 그 위를 덮으면 안 보인다(2026-08-04 사용자 보고) — 위로. */
.b05-drainage .ui-workflow-overlay__handle--side {
z-index: 6;
right: auto;
left: calc(-1 * var(--spacing-24));
border-right: 0;
border-radius: var(--radius-buttons) 0 0 var(--radius-buttons);
}
.b05-drainage__header {
display: flex;
flex: 0 0 auto;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-8);
padding: var(--spacing-8) calc(var(--spacing-8) + var(--spacing-4));
border-bottom: 1px solid var(--color-border);
}
.b05-drainage__header h3 {
margin: 0;
color: var(--color-text);
font-size: var(--text-body-sm);
}
/* 지도 좌상단 오버레이 — 표시 필터 버튼 줄 + 상태 문구(2026-08-17 사용자 지시로
헤더에서 옮겼다). 버튼 자체 여백과 별개로 지도 모서리에서 조금 띄운다.
우측 폭을 묶는 이유: 화면 오른쪽 진행단계 오버레이(z-index 200)가 지도 위를
덮어, 여기까지 늘어난 버튼은 눌리지 않는다. 그 폭(패널 절반 + 우측 여백)을 뺀다. */
.b05-drainage__map-overlay {
position: absolute;
z-index: 2;
top: var(--spacing-8);
left: var(--spacing-8);
display: flex;
flex-direction: column;
gap: var(--spacing-4);
/* 오버레이 빈 자리로는 지도 조작(팬·유역 고르기)이 그대로 통해야 한다. */
pointer-events: none;
}
/* 필터 버튼은 한 줄로 둔다 — 오른쪽 진행단계 오버레이에 끝이 가려도 창을 넓히거나
그 오버레이를 접으면 드러난다(2026-08-17 사용자 확정). 두 줄로 접는 쪽이 지도를
더 많이 가린다. */
.b05-drainage__layers {
display: flex;
flex-wrap: nowrap;
gap: var(--spacing-4);
pointer-events: auto;
}
/* 표시 토글 버튼 — B04 지도 레이어 버튼(.b04-map__layer-button--gis)과 같은 양식을 쓴다.
크기(패딩·글자)만 이 패널 기준을 유지한다(2026-08-01 사용자 지시). */
.b05-drainage__layer-button {
/* 한 줄 유지 — 좁아져도 글자가 접히거나 쭈그러들지 않게 크기를 고정한다. */
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 지도와 같은 조작). */
.b05-drainage__context-menu {
position: absolute;
z-index: 6;
display: flex;
flex-direction: column;
min-width: 130px;
padding: var(--spacing-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: 0 4px 16px rgb(0 0 0 / 25%);
}
.b05-drainage__context-menu[hidden] {
display: none;
}
.b05-drainage__context-menu-item {
padding: var(--spacing-8) var(--spacing-12);
border: 0;
border-radius: var(--radius-cards);
background: transparent;
color: var(--color-text-body);
font-size: 13px;
text-align: left;
cursor: pointer;
}
.b05-drainage__context-menu-item:hover {
background: var(--color-surface-sunken);
}
.b05-drainage__viewport {
position: relative;
flex: 1 1 auto;
min-height: 0;
overflow: hidden;
background: var(--color-surface);
/* 좌버튼은 유역선·배관 마커를 고르는 데만 쓴다. 팬 중(가운데 버튼)에만 JS가
grabbing으로 바꾼다(2026-08-01 사용자 지시). */
cursor: default;
user-select: none;
touch-action: none;
}
.b05-drainage__image,
.b05-drainage__canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.b05-drainage__image {
object-fit: contain;
transform-origin: center;
user-select: none;
pointer-events: none;
}
.b05-drainage__canvas {
pointer-events: none;
}
/* 상태 문구는 좌상단 오버레이가 세로로 쌓아 준다 — 필터 버튼 줄 바로 아래. */
.b05-drainage__status {
color: var(--color-text-secondary);
font-size: var(--text-caption);
pointer-events: none;
}
.b05-drainage__analyze {
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 마진 해제). */
.b05-drainage__tool {
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 사용자 지시).
높이를 비율로 잡으면 유역 수와 무관하게 잘려 몇 개인지 가늠이 안 된다. */
.b05-drainage__basins {
--b05-basin-row: 34px;
display: flex;
max-height: calc(3 * var(--b05-basin-row) + 2 * 2px + 2 * var(--spacing-8));
flex: 0 0 auto;
flex-direction: column;
gap: 2px;
overflow-y: auto;
padding: var(--spacing-8);
border-top: 1px solid var(--color-border);
}
/* 관 개수·세부유역 수·종단 Z 출처 한 줄. */
.b05-drainage__summary {
flex: 0 0 auto;
padding: var(--spacing-4) var(--spacing-8);
border-top: 1px solid var(--color-border);
color: var(--color-text-muted, var(--color-text-body));
font-size: 12px;
}
.b05-drainage__summary[hidden] {
display: none;
}
.b05-drainage__basin {
display: flex;
min-height: var(--b05-basin-row);
align-items: center;
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);
text-align: left;
cursor: pointer;
}
.b05-drainage__basin:hover,
.b05-drainage__basin.is-selected {
border-color: var(--color-border);
background: var(--color-surface);
}
/* 지도 위 서클 번호와 같은 파스텔 색을 써서 목록 항목과 유역을 눈으로 잇는다. */
.b05-drainage__basin-index {
display: inline-flex;
width: 20px;
height: 20px;
flex: 0 0 auto;
align-items: center;
justify-content: center;
border: 1px solid var(--color-border);
border-radius: 50%;
color: #1f2937;
font-size: 11px;
font-weight: 600;
}
.b05-drainage__basin-metrics {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 구조물(배관) 배치로 갈라진 구배 블록 — 평소엔 값 없이 윤곽만, 선택하면 강조와 함께 값 표시. */
.b05-profile-table__segment.is-structure {
opacity: 0.55;
}
.b05-profile-table__segment.is-structure.is-highlight {
opacity: 1;
background: color-mix(in srgb, var(--color-primary) 14%, transparent);
outline: 1px solid var(--color-primary);
}