Files
Aislo/B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style_Cross.css
T
eomsangdonandClaude Opus 5 f35ca7e1c9 feat(B06): 유토곡선(Mass Haul Diagram) 패널 — 종단면도와 한 슬라이드 패널로 결합
참고 도면 `13 유토곡선(울진 울진 대흥 산65 외2(3공구)).pdf` 분석 결과를 반영해
B06 종단면도 바로 아래에 유토곡선을 추가하고, 둘을 접이식 상단 슬라이드 패널
하나로 묶었다.

계산 (프론트 전담, 확정 시에만 영구 저장)
- `_UI_MassHaul.ts` 신설: 평균단면법으로 구간 토량을 만들고 토량환산계수로 기준을
  통일해 누가토량을 누적하는 순수 함수. DOM 의존 0이라 B08/B09가 재사용 가능.
- 기준상태는 다짐상태 — 운반거리 산정 시 모든 수량은 다짐상태로 환산하고 내역서
  수량은 자연상태로 한다(국도건설공사 설계실무 요령). 내역서용 자연상태 수량은
  cut_natural_m3로 함께 보존한다.
- 지반유형이 다른 구간은 각 측점이 자기 절반(단면적 × Δd / 2)을 자기 지반유형으로
  가져간다. 두 절반의 합이 평균단면법과 정확히 일치한다.
- 측점 조작마다 백엔드를 왕복하지 않고 메모리에서 계산하며, 확정 시점에만
  longitudinal_sections.data.mass_haul에 저장한다.

X축 정렬
- longitudinalMaxChainage()를 공용으로 신설해 종단 렌더러와 유토곡선이 같은 X
  매핑을 쓰게 했다. 두 SVG는 chart-wrap 하나를 공유해 가로 스크롤도 함께 움직인다.

패널 높이
- 아래 경계 리사이저로 사용자가 조절한다. 기본값 이상으로 키우면 종단도는 220px을
  지키고 늘어난 몫은 유토곡선이 먹으며, 기본값보다 줄이면 둘이 같은 비율로 작아진다.
- 종단도가 줄면 Y스케일도 그 높이로 다시 잡아 표고가 잘리지 않게 했다.

토량환산계수
- config_system.py 5-4-3절 신설(유일한 정의처). 표준품셈 암종별 범위를 B06 지반유형
  3종에 대응시킨 제안값이며 현장 조정이 전제라 env 없이 상수로 두었다.

검증: tsc 0건, ruff 통과, prettier 적용, 전 파일 700줄 이하.
계산 엔진 테스트 10종 전건 통과(평균단면법·다짐환산·지반유형 배분·비정규 측점
간격·정렬·중복 측점·계수 부재 폴백·페이로드 반올림). 서버 기동 및 번들 포함 확인.

Phase 2(평형선·운반토량 Q·평균운반거리 L·장비 배분·M.N)는 장비 거리 경계와 M.N
정의가 미확정이라 제외했다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 09:02:52 +09:00

659 lines
16 KiB
CSS

/* =============================================================================
* B06_wf3_ProfileCross_UI_Style_Cross.css
* 종·횡단 도면 뷰(섹션·카드·차트·설계 컨트롤·줌/팬·오버레이) 스타일.
* 700줄 제한 대응으로 _UI_Style.css에서 분리(패널/폼/모식도는 원본 유지).
* ========================================================================== */
/* --- 종·횡단 도면 --- */
.b06-profile__main,
.b06-section {
display: flex;
flex-direction: column;
gap: var(--spacing-24);
min-width: 0;
}
.b06-section {
box-sizing: border-box;
padding-inline: var(--spacing-24);
}
.b06-section__panel,
.b06-cross-card {
overflow: hidden;
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
}
/* 상단 슬라이드 패널(종단면도 + 유토곡선).
높이는 인라인이 아니라 CSS 변수로만 받는다 — 인라인 height는 접기 규칙을 이겨
패널이 접히지 않는다(ui_template_resizer.ts 주석). sticky는 그 자체로 위치 지정
요소라 리사이저 손잡이(absolute)의 기준이 되므로 position: relative가 따로 필요 없다. */
.b06-section__panel {
position: sticky;
z-index: 2;
top: 0;
display: flex;
flex-direction: column;
height: var(--b06-profile-height, auto);
}
/* 접었을 때는 저장된 높이를 무시하고 제목 행만 남긴다(클래스 2개라 위 규칙을 이긴다). */
.b06-section__panel.is-collapsed {
height: auto;
}
/* 공용 리사이저는 손잡이를 패널 위쪽 경계에 두지만(B05 하단 패널 기준),
이 패널은 화면 위에 붙어 있으므로 아래쪽 경계로 옮긴다(아래로 끌면 커짐). */
.b06-section__panel > .ui-resizer--vertical {
top: auto;
bottom: -3px;
}
.b06-section__panel-body {
display: flex;
flex: 1;
flex-direction: column;
gap: var(--spacing-8);
min-height: 0;
padding-bottom: var(--spacing-8);
overflow: hidden;
}
.b06-section__panel > header,
.b06-cross-card > header,
.b06-cross-card > footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
padding: var(--spacing-8) var(--spacing-16);
background: var(--color-surface);
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
.b06-section__panel > header {
border-bottom: 1px solid var(--color-border);
}
.b06-section__panel > header h3,
.b06-section__heading h3 {
font-size: var(--text-body);
}
/* 종단면도와 유토곡선이 이 컨테이너 하나를 공유한다 — 가로 스크롤을 함께 써야
같은 측점이 두 그래프에서 같은 자리에 선다. wrap을 나누면 정렬이 깨진다. */
.b06-section__chart-wrap {
display: flex;
flex: 1;
flex-direction: column;
width: 100%;
min-height: 0;
overflow-x: auto;
}
/* ── 유토곡선 (Mass Haul Diagram) ─────────────────────────────────────
색상은 theme.css 변수만 쓴다. 누가토량 곡선은 참고 도면의 자주색에 대응하는
브랜드 보라(계획고 마커와 같은 계열)로 둔다. */
.b06-masshaul__curve {
fill: none;
stroke: var(--color-royal-amethyst);
stroke-width: 2;
stroke-linejoin: round;
}
/* 곡선과 0선 사이 — 절토 우세/성토 우세 구간을 한눈에 가르는 옅은 면. */
.b06-masshaul__band {
fill: var(--color-royal-amethyst);
opacity: 0.12;
stroke: none;
}
/* 0선: 절토 우세와 성토 우세의 경계라 격자보다 진하게. */
.b06-masshaul__zero {
stroke: var(--color-text-secondary);
stroke-width: 1.2;
stroke-dasharray: 4 3;
}
.b06-masshaul__summary {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--spacing-8) var(--spacing-16);
padding: var(--spacing-8) var(--spacing-16);
border-top: 1px solid var(--color-border);
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
.b06-masshaul__chip {
display: inline-flex;
align-items: baseline;
gap: var(--spacing-8);
}
.b06-masshaul__chip em {
font-style: normal;
}
.b06-masshaul__chip strong {
color: var(--color-text-body);
font-variant-numeric: tabular-nums;
}
.b06-masshaul__basis {
margin-inline-start: auto;
color: var(--color-text-muted);
}
.b06-section__chart {
display: block;
max-width: none;
}
/* 횡단도 줌·팬 영역(E-5) — 휠 줌/드래그 팬. */
.b06-cross-card__chart-wrap {
position: relative;
}
.b06-cross-card__chart-wrap .b06-section__chart {
cursor: grab;
touch-action: none;
}
.b06-cross-card__chart-wrap .b06-section__chart:active {
cursor: grabbing;
}
/* 확대해도 선 굵기 고정(E-5) — 줌 시 해상도 저하처럼 보이던 문제 해소. */
.b06-section__chart line,
.b06-section__chart polyline,
.b06-section__chart polygon,
.b06-section__chart rect {
vector-effect: non-scaling-stroke;
}
.b06-cross-card > .b06-section__chart,
.b06-cross-card__chart-wrap > .b06-section__chart {
width: 100%;
}
.b06-section__heading {
display: flex;
align-items: end;
justify-content: space-between;
gap: var(--spacing-16);
color: var(--color-text-secondary);
font-size: var(--text-caption);
}
.b06-section__grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(480px, 100%), 1fr));
gap: var(--spacing-16);
}
.b06-cross-card {
cursor: pointer;
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast);
}
/* 마우스 hover 회색 강조는 선택 강조(빨강)보다 하위 — 미선택 카드에만 적용해
:not(...):hover 특정도가 selected 규칙을 넘어 빨강 테두리를 덮지 않게 한다. */
.b06-cross-card:not(.b06-cross-card--selected):hover {
border-color: var(--color-text-muted);
box-shadow: var(--shadow-sm);
}
.b06-cross-card--selected {
border-color: var(--color-danger);
box-shadow: 0 0 0 2px var(--color-danger);
}
/* 제목행 1행 구조(E-2/E-3): 측점 라벨 → 단면유형 pill → 지반유형 → 구조물·kind. */
.b06-cross-card > header {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: var(--spacing-8);
border-bottom: 1px solid var(--color-border);
overflow-x: auto;
}
.b06-cross-card__title,
.b06-cross-card > header > .b06-cross-card__meta {
display: flex;
align-items: baseline;
gap: var(--spacing-8);
flex: 0 0 auto;
}
/* 지반유형 세그먼트(D-6): 제목행 1행 내 인라인 배치. 좌측 구분선(3번). */
.b06-design__seg--header {
flex: 0 0 auto;
justify-content: flex-start;
}
.b06-cross-card__ground {
padding-left: var(--spacing-8);
border-left: 1px solid var(--color-border);
}
/* 단면유형 pill(E-3) — 구조물 pill과 동일 표기. */
.b06-cross-card__mode {
flex: 0 0 auto;
padding: 1px 6px;
border-radius: var(--radius-pills);
background: color-mix(in srgb, var(--color-info, rgb(37 99 235)) 14%, transparent);
color: var(--color-info, rgb(37 99 235));
font-size: var(--text-caption);
white-space: nowrap;
}
/* 단면유형 pill을 지반유형 우측·우측 맞춤으로 밀어 붙인다(1번). meta는 그 뒤에 따라온다. */
.b06-cross-card__mode--right {
margin-left: auto;
}
.b06-cross-card > header strong {
color: var(--color-text);
font-size: var(--text-body-sm);
}
/* 비정규 측점의 구조물 라벨 — kind(일반측점/BP/EP) 좌측에 자수정색 pill로 표기. */
.b06-cross-card__structure {
padding: 1px 6px;
border-radius: var(--radius-pills);
background: color-mix(in srgb, var(--color-royal-amethyst, rgb(109 40 217)) 16%, transparent);
color: var(--color-royal-amethyst, rgb(109 40 217));
font-size: var(--text-caption);
white-space: nowrap;
}
.b06-cross-card > footer {
border-top: 1px solid var(--color-border);
}
.b06-section__empty {
display: flex;
min-height: 180px;
align-items: center;
justify-content: center;
padding: var(--spacing-24);
border: 1px dashed var(--color-text-muted);
color: var(--color-text-muted);
background: var(--color-surface);
}
/* SVG 차트 색상은 테마 토큰만 사용한다. */
.b06-chart__bg {
fill: var(--color-surface-raised);
}
.b06-chart__grid {
stroke: var(--color-border);
stroke-width: 1;
}
.b06-chart__axis {
stroke: var(--color-text-secondary);
stroke-width: 1;
}
.b06-chart__tick,
.b06-chart__station-label,
.b06-chart__axis-label {
fill: var(--color-text-secondary);
font-family: var(--font-body);
}
.b06-chart__tick,
.b06-chart__station-label {
font-size: 10px;
}
/* 구조물(비정규) 측점 이름 — 종단면도 측점선 상단 표기. */
.b06-chart__structure-label {
font-size: 9px;
font-weight: var(--font-weight-medium);
fill: rgb(180 83 9);
}
.b06-chart__axis-label {
fill: var(--color-text-body);
font-size: var(--text-caption);
font-weight: var(--font-weight-medium);
}
.b06-chart__profile {
fill: none;
stroke: var(--color-chart-0);
stroke-width: 2.5;
}
.b06-chart__cross-profile {
fill: none;
stroke: var(--color-chart-1);
stroke-width: 2.4;
}
/* 종단 계획선(시공계획고): 지반선과 구분되도록 파선 + 강조색 */
.b06-chart__design-profile {
fill: none;
stroke: var(--color-royal-amethyst);
stroke-width: 2.2;
stroke-dasharray: 8 4;
}
/* 절토(계획고가 지반고보다 낮음) / 성토 구간 음영 */
.b06-chart__band {
stroke: none;
}
.b06-chart__band--cut {
fill: rgb(220 38 38 / 14%);
}
.b06-chart__band--fill {
fill: rgb(37 99 235 / 14%);
}
.b06-chart__balance-boundary {
stroke: var(--color-royal-amethyst);
stroke-width: 1.2;
stroke-dasharray: 3 3;
opacity: 0.55;
}
.b06-chart__station {
cursor: pointer;
}
/* 구조물 측점선은 끌어 옮길 수 있다 — 커서로 그 사실을 알린다(2026-08-01 사용자 지시). */
.b06-chart__station--draggable {
cursor: ew-resize;
touch-action: none;
}
.b06-chart__station-line {
stroke-width: 1.2;
}
.b06-chart__station-hit {
stroke: transparent;
stroke-width: 14;
pointer-events: stroke;
}
.b06-chart__station-line--bp,
.b06-chart__station-line--regular {
stroke: var(--color-warning);
}
.b06-chart__station-line--ep {
stroke: var(--color-accent);
stroke-dasharray: 4 3;
}
/* 비정규 측점(구조물)은 종단면도에서도 자수정색 파선으로 구분한다(B05 종단 규칙과 정합).
미선택 시 stroke 미정의로 세로선이 안 그려지던 문제 해소. */
.b06-chart__station-line--irregular {
stroke: var(--color-royal-amethyst, rgb(139 92 246));
stroke-width: 1.4;
stroke-dasharray: 5 3;
}
.b06-chart__station-line--selected,
.b06-chart__center-marker {
stroke: var(--color-danger);
stroke-width: 2.5;
}
/* 십자선이 계획 노선(계획고) 위치를 가리킬 때: 종단 계획선과 동일 색으로 대응시킨다 */
.b06-chart__center-marker--design {
stroke: var(--color-royal-amethyst);
}
.b06-chart__station--selected .b06-chart__station-label {
fill: var(--color-danger);
font-weight: var(--font-weight-bold);
}
/* 측점 표준횡단 설계 지정 컨트롤 (카드 헤더 아래) — 좌측 여유 추가(2번).
한 행 고정(nowrap): 넘치는 컨트롤은 다음 행으로 밀지 않고 "⋯" 드롭다운으로 이동(N-2-2). */
.b06-design {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: var(--spacing-8) var(--spacing-16);
padding: var(--spacing-8) 0 var(--spacing-8) var(--spacing-16);
border-bottom: 1px solid var(--color-border);
margin-bottom: var(--spacing-8);
}
/* 오버플로 드롭다운(N-2-2): 넘친 컨트롤을 담는 우측 맞춤 "⋯" 메뉴. 세로 배치. */
.b06-design__more {
position: relative;
flex: 0 0 auto;
margin-left: auto;
}
.b06-design__more-summary {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 22px;
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface);
color: var(--color-text-secondary);
font-size: 0.9rem;
line-height: 1;
cursor: pointer;
list-style: none;
}
.b06-design__more-summary::-webkit-details-marker {
display: none;
}
.b06-design__more[open] .b06-design__more-summary {
color: var(--color-surface);
background: var(--color-royal-amethyst);
border-color: var(--color-royal-amethyst);
}
.b06-design__more-panel {
position: absolute;
right: 0;
top: calc(100% + 4px);
z-index: 3;
display: flex;
flex-direction: column;
gap: var(--spacing-8);
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
background: var(--color-surface-raised);
box-shadow: var(--shadow-sm);
}
.b06-design__seg {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
.b06-design__seg-legend {
font-size: 0.72rem;
color: var(--color-text-secondary);
white-space: nowrap;
}
/* 선행 조건 미충족 옵션(E-6) — 상시 노출하되 흐리게·클릭 불가. */
.b06-design__seg--disabled {
opacity: 0.4;
}
.b06-design__seg--disabled .b06-design__btn {
cursor: not-allowed;
}
.b06-design__seg-buttons {
display: inline-flex;
border: 1px solid var(--color-border);
border-radius: var(--radius-inputs);
overflow: hidden;
}
.b06-design__btn {
padding: 3px 8px;
font-size: 0.72rem;
color: var(--color-text-secondary);
background: var(--color-surface);
border: none;
border-left: 1px solid var(--color-border);
cursor: pointer;
}
.b06-design__btn:first-child {
border-left: none;
}
.b06-design__btn--active {
color: var(--color-surface);
background: var(--color-royal-amethyst);
font-weight: var(--font-weight-medium);
}
.b06-design__areas {
display: inline-flex;
gap: var(--spacing-8);
margin-left: auto;
font-size: 0.72rem;
font-family: var(--font-mono);
}
/* 암 경계선 제어(E-7) — 그래프 X축 제목 행 우측 맞춤. */
.b06-cross-card__rockb {
position: absolute;
right: var(--spacing-8);
bottom: 2px;
background: color-mix(in srgb, var(--color-surface) 82%, transparent);
border-radius: var(--radius-inputs);
padding: 1px 4px;
}
/* 절·성토 면적 오버레이(E-4) — 그래프 중상단, 배경색으로 그래프 선과 겹쳐도 가독. */
.b06-cross-card__areas {
position: absolute;
top: var(--spacing-8);
left: 50%;
transform: translateX(-50%);
display: inline-flex;
gap: var(--spacing-8);
padding: 2px 8px;
border-radius: var(--radius-inputs);
background: color-mix(in srgb, var(--color-surface) 82%, transparent);
font-size: 0.72rem;
font-family: var(--font-mono);
white-space: nowrap;
pointer-events: none;
}
.b06-design__area--cut {
color: rgb(220 38 38);
}
.b06-design__area--fill {
color: rgb(37 99 235);
}
.b06-design__area--unset {
color: var(--color-text-muted);
}
/* 횡단 표준단면 설계선 오버레이. 기본은 실선. */
.b06-chart__design-cross {
fill: none;
stroke: var(--color-royal-amethyst);
stroke-width: 1.8;
stroke-linejoin: round;
}
/* 지면선과 겹치는 구간만 점선(dash:gap = 1:1)으로 그려 뒤 지표선이 빈 칸으로 비쳐 보이게 한다. */
.b06-chart__design-cross--overlap {
stroke-dasharray: 4 4;
}
/* 차도·노견 경계 짧은 수직 틱(N-4-2) — 설계선과 같은 계열, 얇게. */
.b06-chart__carriageway-tick {
stroke: var(--color-royal-amethyst);
stroke-width: 1.2;
}
/* 암 경계선(설계선 복사 + 오프셋): 리핑암·발파암 구간 점선 */
.b06-chart__rock-boundary {
fill: none;
stroke: var(--color-warning);
stroke-width: 1.6;
stroke-dasharray: 6 4;
stroke-linejoin: round;
opacity: 0.9;
}
/* 포장층 박스: 노면 양 끝점 기준 두께만큼 하향 채움 */
.b06-chart__pavement {
fill: color-mix(in srgb, var(--color-text-secondary) 30%, transparent);
stroke: var(--color-text-secondary);
stroke-width: 1;
}
/* 암 경계선 상/하/리셋 제어 (B05 측점 선 제어 ▲/▼/↺ 버튼 패턴 재활용) */
.b06-design__rockb-btn {
width: 22px;
height: 22px;
padding: 0;
font-size: 0.7rem;
line-height: 1;
color: var(--color-text-secondary);
background: var(--color-surface);
border: none;
border-left: 1px solid var(--color-border);
cursor: pointer;
}
.b06-design__rockb-btn:first-child {
border-left: none;
}
.b06-design__rockb-btn:hover {
color: var(--color-text);
background: var(--color-surface-raised);
}
.b06-design__rockb-btn.is-reset {
color: var(--color-warning);
}
.b06-design__rockb-readout {
padding: 0 var(--spacing-8);
font-size: 0.72rem;
font-family: var(--font-mono);
color: var(--color-warning);
align-self: center;
}
/* 포장 제안 배지: B05 법정 경사 분석이 포장을 권장한 측점 표시 */
.b06-design__paved-badge {
font-size: 0.72rem;
color: var(--color-warning);
cursor: help;
}