Files
Aislo/ui_template/ui_template_theme.css
T
eomsangdonandClaude Opus 5 4d9f8db33d feat(B04): 도로 유입 흐름 강도 표시 + 유입 집중점 마커, 사이드 패널 컨테이너 정리
흐름 강도 계산은 이미 trace_flow()가 원본 1m 격자에서 하고 있었고 03_road_routing.npz에
저장까지 되어 있었다. 이번 작업은 그 원본값을 밖으로 꺼내 화면에 그리는 것이다.

- 강도 곡선 출력 간격을 5m -> 1m로 바꿔 계산 원본을 그대로 내보낸다(구간 합, 평균 아님).
  구간 기준을 round -> floor로 바로잡았다. 도로 셀 누가거리가 전부 0.5 배수라
  numpy의 짝수 반올림이 짝수 칸에 3배를 몰아 2m 주기 빗살을 만들고 있었다
  (도로가 격자축과 나란한 구간은 홀수 칸이 전부 0이라 색이 점선으로 나왔다).
- find_inflow_hotspots(): 도로를 시점·기본 관(세류 교차)·종점으로 잘라 구역을 만들고
  구역마다 floor(길이 / 관 최대간격) + 3개를 뽑는다. 강도 최대점부터 고르되 고른 지점
  양 편측으로 관 최소간격만큼을 후보에서 빼 한쪽 쏠림을 막는다. 관 쪽 경계에서도 같은
  거리만큼 물러난다 - 그러지 않으면 그 관이 이미 받는 물 전부가 관 옆 칸에서 다시
  최대값으로 잡혀, 정작 관이 없어 보충이 필요한 자리가 뽑히지 못한다.
- GET /api/projects/{id}/drainage/road-inflow 신설(B04_wf1_Surface_Router_Inflow.py).
  저장된 road_slot으로 그 1m 구간에 귀속된 셀 마스크를 만들어 외곽선만 돌려준다.
  새 그래프 탐색 없음. npz는 mtime 키로 프로세스 캐시에 둔다.
- 응답에 schema_version을 넣어 형식이 바뀌면 옛 저장분을 무시하고 다시 계산하게 했다.
- 프론트: B04_wf1_Surface_UI_FlowStrength.ts 신설. 계획선 위에 1m 구간 로그 레인보우와
  유입 집중점 마커(크기=강도, 번호)를 얹고, 마커를 고르면 기여 셀 외곽선을 겹쳐 그린다.
  마커 안쪽만 선택 대상이며 팬은 가운데 버튼 전용이라는 규칙은 그대로 둔다.
  좌표 변환은 MapRender에 모았다(normalizedToScreen / lonLatToScreen / metricToScreen).
- 사이드 패널: 지면 필터·서피스·스무딩을 "지표면 분석" 한 컨테이너로 모으고 스무딩을
  드롭다운(적용/미적용)으로 바꿨다. 포인트·모델 표시 옵션은 "모델 표시 옵션" 하나로
  합치고 맨 아래에 점 크기·밀도를 [값][게이지] 형태로 놓았다.
- DRAINAGE_ROAD_WIDTH_M 주석에 설계 도로폭이 아니라 D8 대각 통과를 막는 해석용 굽기
  폭임을 명시했다.

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

480 lines
18 KiB
CSS

/* =============================================================================
* ui_template_theme.css
* 글로벌 디자인 테마 및 CSS 변수 (라이트/다크)
*
* 출처: .agent/design.md — "Wiza" 스타일 레퍼런스
* 원칙:
* - 모든 색상/간격/폰트는 이 파일의 CSS 변수(var(--...))로만 참조.
* - 하드코딩된 Hex/px 값을 컴포넌트에 직접 입력 금지 (frontend.md §1).
* - 8px radius가 시그니처. pill/tag만 --radius-pills 사용.
* ========================================================================== */
:root {
/* ---------------------------------------------------------------------------
* 1. 색상 토큰 (Colors) — 바이올렛 단색 팔레트
* ------------------------------------------------------------------------ */
--color-deep-iris: #26114a; /* 헤드라인, filled 버튼 배경 (24px 이하) */
--color-plum-velvet: #312749; /* 네비 텍스트, 보조 제목, 카드 타이틀 */
--color-royal-amethyst: #3e0079; /* 링크, 포커스 링, 아이콘 하이라이트 */
--color-mist-violet: #edecff; /* 소프트 하이라이트 배경, 태그 배경 */
--color-lavender-wash: #b99aff; /* 그라디언트 스톱, 히어로 분위기 */
--color-twilight-beam: #cf8aff; /* 멀티스톱 장식 그라디언트 시작색 */
/* 중립 (Neutrals) */
--color-canvas: #ffffff; /* 페이지 배경, 카드 표면, 입력 필드 */
--color-paper: #f6f7fa; /* 미묘한 표면 리프트, 테이블 교차행 */
--color-mist: #e6e2e3; /* 헤어라인 보더, 구분선 */
--color-smoke: #c1c7cf; /* 비활성 상태, 스켈레톤 플레이스홀더 */
--color-ash: #9491a1; /* 뮤트 헬퍼 텍스트, placeholder */
--color-slate: #615e6e; /* 보조 본문 텍스트, 설명 */
--color-charcoal: #333333; /* 본문 텍스트, 링크, 아이콘 스트로크 */
--color-carbon: #222222; /* 다크 UI 요소, 니어블랙 액센트 */
--color-ink: #000000; /* 최대 대비 텍스트/보더 */
/* 상태 색상 (Status) — 바이올렛 계열 유지, 필수 기능색만 최소 추가 */
--color-success: #1f8a4c; /* 성공/승인 */
--color-warning: #b8860b; /* 경고 (design.md 별색 #f5b800 계열) */
--color-danger: #c0392b; /* 에러/삭제 */
--color-info: var(--color-royal-amethyst);
/* 그라디언트 (Gradient System) — 항상 흰 표면 뒤에만 배치, 텍스트 직상단 금지 */
--gradient-lavender-wash: linear-gradient(
90deg,
rgb(185, 154, 255),
rgb(125, 67, 255) 10%,
rgba(125, 67, 255, 0)
);
--gradient-twilight-beam: linear-gradient(
to right,
rgb(207, 138, 255),
rgb(255, 102, 193),
rgb(255, 173, 116),
rgb(170, 129, 255)
);
--gradient-section-tint: linear-gradient(
to bottom,
var(--color-canvas),
var(--color-mist-violet)
);
/* ---------------------------------------------------------------------------
* 2. 의미 기반 별칭 (Semantic Aliases)
* 컴포넌트는 가급적 아래 별칭을 사용해 라이트/다크 전환에 대응.
* ------------------------------------------------------------------------ */
--color-bg: var(--color-canvas); /* 페이지 배경 */
--color-surface: var(--color-paper); /* 카드/패널 표면 */
--color-surface-raised: var(--color-canvas); /* 떠 있는 표면 */
--color-border: var(--color-mist); /* 기본 보더 */
--color-text: var(--color-deep-iris); /* 기본 본문 (제목 강조) */
--color-text-body: var(--color-charcoal); /* 일반 본문 */
--color-text-secondary: var(--color-slate); /* 보조 텍스트 */
--color-text-muted: var(--color-ash); /* 뮤트/placeholder */
--color-primary: var(--color-deep-iris); /* 주 액션 배경 */
--color-primary-text: var(--color-canvas); /* 주 액션 위 텍스트 */
--color-accent: var(--color-royal-amethyst); /* 링크/액센트 */
--color-focus-ring: var(--color-royal-amethyst);
/* 차트 시리즈 색상 (CPU/메모리/디스크 등 구분용) */
--color-chart-0: #5b8def; /* 파랑 — CPU */
--color-chart-1: #34c38f; /* 초록 — 메모리 */
--color-chart-2: #f1963b; /* 주황 — 디스크 */
--color-chart-3: #9b6dde; /* 보라 — 예비 */
/* ---------------------------------------------------------------------------
* 3. 타이포그래피 (Typography)
* ------------------------------------------------------------------------ */
/* 폰트 패밀리 — 한글은 Noto Sans KR 우선 (resources/fonts) */
--font-display:
"Britti Sans", "Plus Jakarta Sans", "Noto Sans KR", ui-sans-serif, system-ui, -apple-system,
"Segoe UI", Roboto, sans-serif;
--font-body:
"Inter", "Noto Sans KR", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", "Consolas", ui-monospace, monospace;
/* 스케일 (px 고정 — design.md 명세) */
--text-caption: 12px;
--leading-caption: 1.3;
--text-body-sm: 14px;
--leading-body-sm: 1.4;
--text-body: 16px;
--leading-body: 1.5;
--text-subheading: 24px;
--leading-subheading: 1;
--text-heading-sm: 32px;
--leading-heading-sm: 1;
--text-heading: 40px;
--leading-heading: 1;
--text-heading-lg: 56px;
--leading-heading-lg: 1;
--text-display: 64px;
--leading-display: 1;
/* 굵기 */
--font-weight-regular: 400;
--font-weight-medium: 500;
--font-weight-bold: 700;
/* ---------------------------------------------------------------------------
* 4. 간격 (Spacing) — 8px 기반, compact density
* ------------------------------------------------------------------------ */
--spacing-unit: 8px;
--spacing-4: 4px; /* pill/tag 세로 패딩용 하프 유닛 */
--spacing-8: 8px;
--spacing-16: 16px;
--spacing-24: 24px;
--spacing-32: 32px;
--spacing-40: 40px;
--spacing-48: 48px;
--spacing-56: 56px;
--spacing-64: 64px;
--spacing-96: 96px;
--spacing-104: 104px;
--spacing-216: 216px;
/* ---------------------------------------------------------------------------
* 5. 보더 반경 (Border Radius) — 8px 시그니처
* ------------------------------------------------------------------------ */
--radius-sm: 2px;
--radius-lg: 8px; /* 카드/버튼/입력/아이콘 = 시그니처 */
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-3xl: 24px; /* large 컨테이너 */
--radius-pills: 1440px; /* pill/tag/캡슐 네비 전용 */
--radius-cards: var(--radius-lg);
--radius-buttons: var(--radius-lg);
--radius-inputs: var(--radius-lg);
--radius-icons: var(--radius-lg);
--radius-large: var(--radius-3xl);
/* ---------------------------------------------------------------------------
* 6. 그림자 (Shadows) — 블루틴트, 서브틀 (최대 32px blur / 6% opacity)
* ------------------------------------------------------------------------ */
--shadow-sm:
rgba(18, 55, 105, 0.08) 0px 2px 4px 0px, rgba(18, 55, 105, 0.04) 0px 1px 1px 0px,
rgba(18, 55, 105, 0.08) 0px 0px 0px 1px;
--shadow-lg:
rgba(14, 59, 101, 0.06) 0px 32px 24px -12px, rgba(14, 59, 101, 0.01) 0px 11px 4px 0px,
rgba(14, 59, 101, 0.02) 0px 6px 4px 0px, rgba(14, 59, 101, 0.03) 0px 3px 3px 0px,
rgba(14, 59, 101, 0.04) 0px 1px 1px 0px;
--shadow-feature:
rgba(114, 49, 255, 0.32) 0px -6px 20px 0px inset, rgba(47, 1, 151, 0.12) 0px 11px 12px 0px,
rgba(47, 1, 151, 0.12) 0px 3px 3px 0px, rgba(47, 1, 151, 0.12) 0px 1px 1px 0px,
rgba(47, 1, 151, 0.08) 0px 0px 0px 1px;
/* ---------------------------------------------------------------------------
* 7. 레이아웃 (Layout)
* ------------------------------------------------------------------------ */
--page-max-width: 1200px;
--section-gap: 64px;
--card-padding: 24px;
--element-gap: 8px;
/* Scrollbar */
--color-scrollbar-track: transparent;
--color-scrollbar-thumb: color-mix(in srgb, var(--color-text) 12%, transparent);
--color-scrollbar-thumb-hover: color-mix(in srgb, var(--color-text) 20%, transparent);
--color-scrollbar-thumb-active: color-mix(in srgb, var(--color-text) 28%, transparent);
/* 워크플로우 3단 레이아웃 (frontend.md §2) */
--wf-header-height: 56px; /* 상단: 타이틀 + 진행 단계 */
--wf-left-panel-width: 320px; /* 좌측: 입력 폼 (너비 고정) */
/* ---------------------------------------------------------------------------
* 8. 상호작용 (Motion & Z-index)
* ------------------------------------------------------------------------ */
--transition-fast: 120ms ease;
--transition-base: 200ms ease;
--z-header: 100;
--z-dropdown: 200;
--z-overlay: 900;
--z-modal: 1000;
--z-toast: 1100;
/* ---------------------------------------------------------------------------
* 9. 2D 지도 벡터 팔레트 (B04 배경지도 · B05 배수유역도 공용)
* 캔버스에 그리는 색이라 CSS로 직접 칠할 수 없다 — TS가 이 변수를 읽어 쓴다
* (`ui_template_palette.ts`). 여기가 유일한 정의처이며 화면별 복제 금지.
* 배경이 위성사진이라 라이트/다크 공통으로 같은 색을 쓴다.
* ------------------------------------------------------------------------ */
--map-route: #f97316; /* 계획선 (B04 지도·B05 배수유역도 공통) */
--map-cadastral: #f97316;
--map-sigungu: #7c3aed;
--map-eupmyeondong: #22c55e;
--map-contour: #fdba74;
--map-sheet-contour: #a5b4fc;
--map-sheet-stream: #2563eb;
--map-sheet-elev-point: #f9a8d4;
--map-sheet-cutfill: #f43f5e;
--map-sheet-wall: #0f766e;
--map-flow-arrow: #7c3aed;
--map-upstream: rgba(29, 78, 216, 0.95); /* 상류 세류망 강조선 */
--map-upstream-toggle: #1d4ed8; /* 토글 버튼 색띠 */
--map-satellite-toggle: #64748b; /* 배경사진은 선 색이 없어 중립 회색 */
--map-pipe-orphan: #e5e7eb; /* 유역이 없는 배관 마커 */
--map-pipe-marker: rgba(249, 115, 22, 0.95); /* B04 기본 관 마커 */
/* 배수유역 오버레이(B04 지도) */
--map-halo: rgba(255, 255, 255, 0.9); /* 선·글자 뒤에 까는 흰 테두리 */
--map-label-text: #1f2937; /* 지도 위 라벨 글자 */
--map-marker-text: #111827; /* 관 마커 번호 글자·선택된 마커 테두리 */
--map-marker-outline: #374151; /* 선택되지 않은 마커 테두리 */
--map-basin-outline: #92400e; /* 2차 유역 외곽선(분수령) */
--map-basin-ring: rgba(146, 64, 14, 0.95);
--map-boundary-handle: rgba(146, 64, 14, 0.95); /* 유역선 편집 핸들 */
--map-boundary-handle-moved: rgba(220, 38, 38, 0.95); /* 사용자가 옮긴 핸들 */
--map-primary-region: #059669; /* 1차 유역 */
--map-flow-region: #2563eb; /* 유역 방향 토글 */
--map-grid-line: rgba(255, 255, 255, 0.55); /* 해석 격자 셀 선 */
--map-flow-to-road-fill: rgba(220, 38, 38, 0.28);
--map-flow-to-road-line: rgba(153, 27, 27, 0.95);
--map-flow-away-fill: rgba(37, 99, 235, 0.22);
--map-flow-away-line: rgba(255, 255, 255, 0.95);
--map-flow-away-arrow: rgba(30, 64, 175, 0.95);
--map-flow-unknown-fill: rgba(120, 113, 108, 0.18); /* 표고가 없어 판정 못한 셀 */
/* 도로 유입 흐름 강도 색띠 — 적음(파랑) → 많음(빨강). 로그 스케일로 보간한다.
보간을 하려면 hex여야 한다(rgba/색이름 금지). */
--map-flow-ramp-0: #2563eb;
--map-flow-ramp-1: #06b6d4;
--map-flow-ramp-2: #22c55e;
--map-flow-ramp-3: #eab308;
--map-flow-ramp-4: #f97316;
--map-flow-ramp-5: #dc2626;
--map-inflow-outline: #dc2626; /* 선택한 지점으로 들어오는 셀들의 외곽선 */
/* 유역 채움 파스텔 8종 — 유역 번호 순으로 돌려쓴다(사용자 지시: 파스텔톤).
알파 0.45는 비선택 유역을 0.18로 낮출 때 문자열로 치환하므로 형식을 바꾸지 말 것. */
--map-basin-1: rgba(167, 216, 199, 0.45);
--map-basin-2: rgba(247, 208, 168, 0.45);
--map-basin-3: rgba(186, 199, 240, 0.45);
--map-basin-4: rgba(241, 183, 199, 0.45);
--map-basin-5: rgba(214, 226, 168, 0.45);
--map-basin-6: rgba(202, 186, 227, 0.45);
--map-basin-7: rgba(168, 214, 232, 0.45);
--map-basin-8: rgba(240, 219, 168, 0.45);
}
/* =============================================================================
* 다크 모드 (Dark) — 라이트 우선 브랜드이나 CAD 뷰어 야간 작업 대응
* design.md는 light 브랜드이므로, 다크는 중립 표면을 어둡게 반전하고
* 바이올렛 액센트는 명도를 올려 대비 확보.
* ========================================================================== */
:root[data-theme="dark"] {
--color-canvas: #16121f;
--color-paper: #1e1930;
--color-mist: #322a45;
--color-smoke: #4a4160;
--color-ash: #8b86a0;
--color-slate: #b3aec6;
--color-charcoal: #e4e0f0;
--color-carbon: #f0edf8;
--color-ink: #ffffff;
--color-deep-iris: #d8ccff;
--color-plum-velvet: #c4b8e6;
--color-royal-amethyst: #b99aff;
--color-mist-violet: #2a2140;
--color-bg: var(--color-canvas);
--color-surface: var(--color-paper);
--color-surface-raised: #251f38;
--color-border: var(--color-mist);
--color-text: var(--color-deep-iris);
--color-text-body: var(--color-charcoal);
--color-text-secondary: var(--color-slate);
--color-text-muted: var(--color-ash);
--color-primary: var(--color-royal-amethyst);
--color-primary-text: #16121f;
--color-accent: var(--color-lavender-wash);
--color-focus-ring: var(--color-lavender-wash);
--color-success: #3fbb6f;
--color-warning: #e0b64a;
--color-danger: #e8695a;
--shadow-sm: rgba(0, 0, 0, 0.4) 0px 2px 4px 0px, rgba(0, 0, 0, 0.2) 0px 0px 0px 1px;
--shadow-lg: rgba(0, 0, 0, 0.5) 0px 32px 24px -12px, rgba(0, 0, 0, 0.3) 0px 3px 3px 0px;
}
/* 시스템 설정 기반 자동 다크 (사용자가 명시적으로 data-theme를 안 준 경우) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-canvas: #16121f;
--color-paper: #1e1930;
--color-mist: #322a45;
--color-smoke: #4a4160;
--color-ash: #8b86a0;
--color-slate: #b3aec6;
--color-charcoal: #e4e0f0;
--color-carbon: #f0edf8;
--color-ink: #ffffff;
--color-deep-iris: #d8ccff;
--color-plum-velvet: #c4b8e6;
--color-royal-amethyst: #b99aff;
--color-mist-violet: #2a2140;
--color-bg: var(--color-canvas);
--color-surface: var(--color-paper);
--color-surface-raised: #251f38;
--color-border: var(--color-mist);
--color-text: var(--color-deep-iris);
--color-text-body: var(--color-charcoal);
--color-text-secondary: var(--color-slate);
--color-text-muted: var(--color-ash);
--color-primary: var(--color-royal-amethyst);
--color-primary-text: #16121f;
--color-accent: var(--color-lavender-wash);
--color-focus-ring: var(--color-lavender-wash);
}
}
/* =============================================================================
* 기본 리셋 및 문서 스타일
* ========================================================================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* 일반 페이지의 가로·세로 스크롤바를 동일한 얇은 테마로 표시한다.
* B07 CAD iframe은 별도 문서이므로 이 전역 스타일의 영향을 받지 않는다. */
* {
scrollbar-color: var(--color-scrollbar-thumb) var(--color-scrollbar-track);
scrollbar-width: thin;
}
@supports selector(::-webkit-scrollbar) {
* {
scrollbar-color: auto;
scrollbar-width: auto;
}
*::-webkit-scrollbar {
width: 8px;
height: 8px;
}
*::-webkit-scrollbar-track {
background-color: transparent;
}
*::-webkit-scrollbar-thumb {
border: 2px solid transparent;
border-radius: var(--radius-pills);
background-color: var(--color-scrollbar-thumb);
background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:hover {
background-color: var(--color-scrollbar-thumb-hover);
}
*::-webkit-scrollbar-thumb:active {
background-color: var(--color-scrollbar-thumb-active);
}
*::-webkit-scrollbar-button {
display: none;
width: 0;
height: 0;
}
*::-webkit-scrollbar-corner {
background-color: transparent;
}
}
@media (forced-colors: active) {
* {
scrollbar-color: auto;
}
}
/* ─── 공용 접기 컨테이너 (ui_template_collapsible.ts) ─────────────────────────
컨테이너에 `ui-collapsible`, 제목에 `ui-collapsible__title`을 주면 제목 행 전체가
클릭 영역이 되고, 접히면 제목을 뺀 직속 자식이 숨겨진다. 펼침 ▾ / 접힘 ▸ 캐럿 표시. */
.ui-collapsible__title {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8, 8px);
cursor: pointer;
user-select: none;
}
.ui-collapsible__title::after {
content: "▾";
flex: 0 0 auto;
margin-block: -0.4em;
color: var(--color-text-muted);
/* 레이아웃 허용 범위 내에서 최대한 크게(제목 글자의 약 1.7배). 음수 margin으로 행 높이는 유지. */
font-size: 1.7em;
line-height: 1;
}
.ui-collapsible.is-collapsed > .ui-collapsible__title::after {
content: "▸";
}
.ui-collapsible.is-collapsed > :not(.ui-collapsible__title) {
display: none;
}
html,
body {
margin: 0;
padding: 0;
}
body {
background-color: var(--color-bg);
color: var(--color-text-body);
font-family: var(--font-body);
font-size: var(--text-body);
line-height: var(--leading-body);
font-weight: var(--font-weight-regular);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
font-family: var(--font-display);
font-weight: var(--font-weight-medium);
color: var(--color-text);
line-height: 1;
}
h1 {
font-size: var(--text-heading);
}
h2 {
font-size: var(--text-heading-sm);
}
h3 {
font-size: var(--text-subheading);
}
a {
color: var(--color-accent);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
text-decoration: underline;
}
:focus-visible {
outline: 2px solid var(--color-focus-ring);
outline-offset: 2px;
}