Files
Aislo/B05_wf2_Route/B05_wf2_Route_UI_Style.css
T
2026-07-24 17:52:01 +09:00

793 lines
20 KiB
CSS

.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 {
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 뷰를 전체 영역으로 볼 수 있다. */
.b05-route-profile {
position: relative;
display: flex;
flex: 0 0 60vh;
flex: 0 0 60dvh;
flex-direction: column;
min-height: 0;
overflow: visible;
border-top: 1px solid var(--color-border);
background: var(--color-surface-raised);
transition: flex-basis var(--transition-fast);
}
.b05-route-profile.is-collapsed {
flex-basis: 0;
min-height: 0;
}
.b05-route-profile__body {
box-sizing: border-box;
flex: 1 1 auto;
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 {
display: flex;
flex-direction: column;
}
.b05-profile__chart {
position: relative;
flex: 0 0 auto;
min-height: 0;
}
/* 가로 스크롤에도 고정되는 Y축(표고 눈금). 0크기 sticky 앵커에 축을 절대배치해 레이아웃 불변. */
.b05-profile__yaxis {
position: sticky;
left: 0;
z-index: 3;
width: 0;
height: 0;
}
.b05-profile__yaxis-inner {
position: absolute;
left: 0;
top: 0;
box-sizing: border-box;
/* SVG 배경(b06-chart__bg)과 같은 색으로 스크롤되는 그래프를 가려 값 누출을 막는다. */
background: var(--color-surface-raised);
border-right: 1px solid var(--color-text-secondary);
pointer-events: none;
}
.b05-profile__yaxis-tick {
position: absolute;
right: 6px;
color: var(--color-text-secondary);
font-size: 10px;
white-space: nowrap;
transform: translateY(-50%);
}
.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%;
}
.b05-route__viewer-status {
position: absolute;
inset: var(--spacing-16) auto auto var(--spacing-16);
max-width: 420px;
padding: var(--spacing-8) var(--spacing-16);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface-raised);
color: var(--color-text-body);
font-size: var(--text-caption);
pointer-events: none;
}
.b05-route__view-controls {
position: absolute;
z-index: 2;
top: 58px;
left: var(--spacing-16);
display: flex;
align-items: center;
gap: var(--spacing-8);
}
.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);
}
.b05-route__panel {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
gap: var(--spacing-16);
padding-bottom: var(--spacing-24);
}
.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));
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
}
/* 제목 행 클릭 접기/펼치기는 공용 collapsible(ui_template_theme.css)에서 처리한다. */
.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);
}
.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;
}
.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;
}
.b05-route__irregular-item span {
overflow: hidden;
color: var(--color-text-muted);
font-size: var(--text-caption);
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);
}
.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;
}
.b05-route-profile__balance-reset {
flex: 0 0 auto;
padding: 1px 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;
}
/* ─── 도면 테이블 (구배 ~ 곡선 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;
box-sizing: border-box;
width: var(--b05-table-label-width, 60px);
height: 100%;
padding-inline: 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;
}
/* 측점 값 셀은 측점 수직선을 중심으로 좌우 대칭 배치된다.
셀 경계(= 이웃 측점과의 중간)에 세로 구분선을 둬 구배 행과 같은 격자를 만든다. */
.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);
}
/* 선택된 규칙 측점 열의 값·곡선 셀 하이라이트(비정규 측점 값 열 강조와 같은 색). */
.b05-profile-table__cell.is-selected,
.b05-profile-table__curve.is-selected {
background: color-mix(
in srgb,
var(--color-royal-amethyst, rgb(109 40 217)) 14%,
var(--color-surface)
);
}
.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-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-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);
}
.b05-profile-table__irregular-col-cell.is-plan {
color: var(--color-royal-amethyst, rgb(109 40 217));
font-weight: var(--font-weight-medium);
}
/* 값 열의 계획고 직접 입력 셀 — 열은 pointer-events:none이라 입력만 되살린다. */
.b05-profile-table__irregular-input {
box-sizing: border-box;
width: 92%;
height: 88%;
padding: 0;
border: 1px solid var(--color-royal-amethyst, rgb(109 40 217));
border-radius: 2px;
background: var(--color-surface);
color: var(--color-royal-amethyst, rgb(109 40 217));
font: inherit;
font-weight: var(--font-weight-medium);
text-align: center;
pointer-events: auto;
}
/* ─── 계획고 편집 버튼 (크기 유지·상시 표시·밝은 글자) ───────────────────── */
.b05-profile-edit {
position: absolute;
z-index: 4;
inset: 0;
pointer-events: none;
}
/* 상시 보이게 둔다(예전엔 패널 hover 시에만 노출). 크기(18×15)는 유지하고 글자는 밝게. */
.b05-profile-edit__btn {
position: absolute;
width: 18px;
height: 15px;
padding: 0;
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)에 놓이고, 겹치는 자리에서만
렌더러가 좌우로 비켜 배치한다. 구분을 위해 밝은 자수정색으로 표시한다. */
.b05-profile-edit__btn.is-segment {
border-color: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 55%, transparent);
color: color-mix(in srgb, var(--color-royal-amethyst, rgb(139 92 246)) 85%, var(--color-text));
}
.b05-profile-edit__btn.is-reset {
top: 20px;
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;
}