feat(테마): 그림 전용 값 --draw-* — 종단 · 횡단 · 이정표 SVG · 지도 계획선 · CAD 바탕 · 3D 바탕 색을 화면 테마와 떼어 지금 색 고정 (PLAN 551-2)

- ui_template_theme.css 끝 블록 = --draw-* 17 개 · 밝음 / 어두움 / 시스템 어두움 글자 hex
- B05 · B06 · B08 그림 CSS stroke · fill 125 줄 · --b06-tag = --draw-*
- 캔버스 · 3D 읽기 = B05 노선 편집 지도 · 노선 추천 지도 · 3D 바탕 · B04 3D 바탕 · 지도 MC 글 · D03 고른 측점 · CAD 바탕 · 격자
- 지킴 시험 test_551_2_draw_tokens(화면 변수를 모두 바꿔도 그림 풀린 색 같음)
- 기존 시험 7 개 = 그림 줄이 옛 --color-* 이름을 잡던 단정을 --draw-* 로 고침(값 같음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 20:59:03 +09:00
co-authored by Claude Opus 5.5
parent 85ae6319e4
commit 7e07da4b31
27 changed files with 379 additions and 160 deletions
+1 -1
View File
@@ -468,7 +468,7 @@ export function bindSurfaceViewerTheme(
return;
}
const surfaceRaised = getComputedStyle(document.documentElement)
.getPropertyValue("--color-surface-raised")
.getPropertyValue("--draw-surface-raised")
.trim();
applyBackground(
surfaceRaised && CSS.supports("color", surfaceRaised)
@@ -330,9 +330,9 @@ export function drawStationTicks(
context.translate(-lx, -ly);
}
// 배경을 깔아 등고선 위에서도 읽히게 한다.
context.fillStyle = mc ? themeColor("--color-canvas", "#ffffff") : "rgba(255, 255, 255, 0.78)";
context.fillStyle = mc ? themeColor("--draw-canvas", "#ffffff") : "rgba(255, 255, 255, 0.78)";
context.fillRect(lx - half, ly - 8, width, 16);
context.fillStyle = mc ? themeColor("--color-warning", "#b8860b") : "#222222";
context.fillStyle = mc ? themeColor("--draw-warning", "#b8860b") : "#222222";
context.fillText(label, lx, ly);
context.restore();
}
@@ -118,7 +118,7 @@ export function createRecommendMap(box: HTMLElement): RecommendMap {
context.lineJoin = "round";
const styles: Record<MapLayer["kind"], () => void> = {
expected: () => {
context.strokeStyle = themeColor("--color-text-secondary", "#9ca3af");
context.strokeStyle = themeColor("--draw-text-secondary", "#9ca3af");
context.lineWidth = 1.5;
context.setLineDash([5, 4]);
},
@@ -129,7 +129,7 @@ export function createRecommendMap(box: HTMLElement): RecommendMap {
context.setLineDash([]);
},
preview: () => {
context.strokeStyle = themeColor("--color-primary", "#6d28d9");
context.strokeStyle = themeColor("--draw-primary", "#6d28d9");
context.lineWidth = 3;
context.setLineDash([]);
},
@@ -169,7 +169,7 @@ export function drawRouteEditScene(context: CanvasRenderingContext2D, scene: Rou
const style = getComputedStyle(document.documentElement);
const line = scene.plannedLine.length ? scene.plannedLine : scene.planned;
context.clearRect(0, 0, view.width, view.height);
context.fillStyle = style.getPropertyValue("--color-surface") || "#111";
context.fillStyle = style.getPropertyValue("--draw-surface") || "#111";
context.fillRect(0, 0, view.width, view.height);
// 여기서부터 **그림 전체**가 돈다 — 글자도 함께 돈다(CAD 도면과 같은 방식, 사용자 지시 ⑯).
@@ -246,7 +246,7 @@ export function drawRouteEditScene(context: CanvasRenderingContext2D, scene: Rou
toScreen,
scene.expected,
[6, 5],
style.getPropertyValue("--color-text-secondary") || "#9ca3af",
style.getPropertyValue("--draw-text-secondary") || "#9ca3af",
1.6,
);
// 곡선 시작·끝점 — 잡아서 직선 각도와 R 을 함께 바꾸는 손잡이(2026-09-07 사용자 지시).
@@ -307,7 +307,7 @@ export function drawRouteEditScene(context: CanvasRenderingContext2D, scene: Rou
const bad = (scene.nodeInfo[index]?.violations?.length ?? 0) > 0;
// 고정한 점은 고정 색 — 기준 미달(붉음)이 먼저다.
context.fillStyle = bad
? style.getPropertyValue("--color-danger") || "#dc2626"
? style.getPropertyValue("--draw-danger") || "#dc2626"
: scene.locked[index]
? lockedColor
: style.getPropertyValue("--map-route-node") || "#15803d";
@@ -367,7 +367,7 @@ function drawContourLabels(
style.getPropertyValue("--map-sheet-contour") || "#a5b4fc",
scene.contourStepM,
scene.uprightRad,
style.getPropertyValue("--color-surface") || "#111", // 바탕칠과 같은 색 — 어두움에서 흰 상자 안 생김
style.getPropertyValue("--draw-surface") || "#111", // 바탕칠과 같은 색 — 어두움에서 흰 상자 안 생김
);
if (scene.pickedContour >= 0) drawPickedContourLabel(context, scene, scene.view);
context.restore();
+1 -1
View File
@@ -153,7 +153,7 @@
/* 비정규 측점(구조물)은 그래프에서도 파선 세로선으로 구분해 규칙 측점과 헷갈리지 않게 한다. */
.b05-route-profile .b06-chart__station-line--irregular {
stroke: var(--color-royal-amethyst, rgb(139 92 246));
stroke: var(--draw-accent);
stroke-width: 1.4;
stroke-dasharray: 5 3;
}
@@ -344,7 +344,7 @@
.b05-routeedit__cross-ground {
fill: none;
stroke: var(--color-text-secondary, #94a3b8);
stroke: var(--draw-text-secondary);
stroke-width: 1.6;
vector-effect: non-scaling-stroke;
}
@@ -357,7 +357,7 @@
}
.b05-routeedit__cross-key {
fill: var(--color-text-secondary, #94a3b8);
fill: var(--draw-text-secondary);
font-size: 11px;
}
@@ -503,13 +503,13 @@
.b05-routerec__profile .is-ground {
fill: none;
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1.5;
}
.b05-routerec__profile .is-plan {
fill: none;
stroke: var(--color-primary);
stroke: var(--draw-primary);
stroke-width: 2;
}
+13 -13
View File
@@ -331,7 +331,7 @@
.b05-profile-runmark__line {
fill: none;
stroke: var(--color-danger, #d64545);
stroke: var(--draw-danger);
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
@@ -340,7 +340,7 @@
/* 구간 경계(라운드 시·종점) 세로 표식 — 어디까지 함께 움직이는지 알린다. */
.b05-profile-runmark__edge {
stroke: var(--color-danger, #d64545);
stroke: var(--draw-danger);
stroke-width: 1;
stroke-dasharray: 3 3;
opacity: 0.5;
@@ -356,26 +356,26 @@
}
.b05-profile-freemark__band {
fill: color-mix(in srgb, var(--color-primary) 10%, transparent);
fill: color-mix(in srgb, var(--draw-primary) 10%, transparent);
}
.b05-profile-freemark__edge {
stroke: var(--color-primary);
stroke: var(--draw-primary);
stroke-width: 1.5;
}
/* 범위 「시작–끝 · 길이」 글 — [직선화] · [자유지정] 실시간 띠 위(271-2 · 3). */
.b05-profile-freemark__span {
fill: var(--color-primary);
fill: var(--draw-primary);
font-size: var(--text-caption);
paint-order: stroke;
stroke: var(--color-surface);
stroke: var(--draw-surface);
stroke-width: 3px;
}
/* 지나온 선 · 미리 보기 · 점 = 녹색(367-4) — 범위 띠 · 경계는 보라 그대로. */
.b05-profile-freemark__line {
stroke: var(--color-success);
stroke: var(--draw-success);
stroke-width: 2;
stroke-dasharray: 6 4;
}
@@ -385,25 +385,25 @@
}
.b05-profile-freemark__line.is-over {
stroke: var(--color-danger, #d64545);
stroke: var(--draw-danger);
}
.b05-profile-freemark__dot {
fill: var(--color-surface);
stroke: var(--color-success);
fill: var(--draw-surface);
stroke: var(--draw-success);
stroke-width: 1.5;
}
.b05-profile-freemark__grade {
fill: var(--color-text);
fill: var(--draw-text);
font-size: var(--text-caption);
paint-order: stroke;
stroke: var(--color-surface);
stroke: var(--draw-surface);
stroke-width: 3px;
}
.b05-profile-freemark__grade.is-over {
fill: var(--color-danger, #d64545);
fill: var(--draw-danger);
}
.b05-profile-edit {
+1 -1
View File
@@ -121,7 +121,7 @@ export function createRouteViewer(): RouteViewer {
return;
}
const surfaceRaised = getComputedStyle(document.documentElement)
.getPropertyValue("--color-surface-raised")
.getPropertyValue("--draw-surface-raised")
.trim();
scene.background = new THREE.Color(
surfaceRaised && CSS.supports("color", surfaceRaised)
+6 -6
View File
@@ -205,25 +205,25 @@
.b06-diag__ground {
fill: none;
stroke: var(--color-text-muted);
stroke: var(--draw-text-muted);
stroke-width: 1;
stroke-dasharray: 3 2;
}
.b06-diag__road {
fill: none;
stroke: var(--color-text-body);
stroke: var(--draw-text-body);
stroke-width: 2;
}
.b06-diag__center {
stroke: var(--color-royal-amethyst, rgb(109 40 217));
stroke: var(--draw-accent);
stroke-width: 1;
stroke-dasharray: 4 3;
}
.b06-diag__tick {
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1;
}
@@ -244,12 +244,12 @@
}
.b06-diag__label {
fill: var(--color-text-body);
fill: var(--draw-text-body);
font-size: 14px;
}
.b06-diag__label-sm {
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
font-size: 12px;
}
@@ -14,11 +14,11 @@
/* Y축 중심고 상자 색(379-3) — 원지반 = 원지반선 · 계획 = 설계선. */
.b06-chart__center-tag--ground {
--b06-tag: var(--color-chart-1);
--b06-tag: var(--draw-chart-1);
}
.b06-chart__center-tag--design {
--b06-tag: var(--color-royal-amethyst);
--b06-tag: var(--draw-accent);
}
/* 면적 강조 밴드 — 평소엔 클릭 대상으로만 남고, 켜질 때만 채워진다. */
@@ -30,34 +30,34 @@
/* 밴드 색은 위 면적표 글자색과 짝을 맞춘다 — 어느 숫자를 켰는지 색으로 바로 읽히게. */
.b06-chart__area--cut_soil.is-active > polygon {
fill: color-mix(in srgb, var(--color-chart-2) 28%, transparent);
stroke: var(--color-chart-2);
fill: color-mix(in srgb, var(--draw-chart-2) 28%, transparent);
stroke: var(--draw-chart-2);
stroke-width: 1.2;
}
/* 암반 밴드는 하나지만 색은 암종을 따라간다 — 표의 RR/BR 글자색과 같아야 짝이 읽힌다. */
.b06-chart__area--cut_rr.is-active > polygon {
fill: color-mix(in srgb, var(--color-success) 28%, transparent);
stroke: var(--color-success);
fill: color-mix(in srgb, var(--draw-success) 28%, transparent);
stroke: var(--draw-success);
stroke-width: 1.2;
}
.b06-chart__area--cut_br.is-active > polygon {
fill: color-mix(in srgb, var(--color-chart-3) 28%, transparent);
stroke: var(--color-chart-3);
fill: color-mix(in srgb, var(--draw-chart-3) 28%, transparent);
stroke: var(--draw-chart-3);
stroke-width: 1.2;
}
.b06-chart__area--fill.is-active > polygon {
fill: color-mix(in srgb, var(--color-chart-0) 28%, transparent);
stroke: var(--color-chart-0);
fill: color-mix(in srgb, var(--draw-chart-0) 28%, transparent);
stroke: var(--draw-chart-0);
stroke-width: 1.2;
}
/* 횡단 표준단면 설계선 오버레이. 기본은 실선. */
.b06-chart__design-cross {
fill: none;
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
stroke-width: 1.8;
stroke-linejoin: round;
}
@@ -80,7 +80,7 @@
/* 마우스 올림 = 고른 모양(379-1) · 고름은 누를 때만 */
.b06-chart__design-hit:hover,
.b06-chart__design-hit.is-active {
stroke: color-mix(in srgb, var(--color-royal-amethyst) 45%, transparent);
stroke: color-mix(in srgb, var(--draw-accent) 45%, transparent);
stroke-width: 7;
}
@@ -92,7 +92,7 @@
/* 원지반 십자선(413) = 원지반선 색 · 계획 십자선 아래(먼저 그림) */
.b06-chart__ground-center-marker {
stroke: var(--color-chart-1);
stroke: var(--draw-chart-1);
stroke-width: 2.5;
}
@@ -104,8 +104,8 @@
}
.b06-chart__center-hit.is-active {
fill: color-mix(in srgb, var(--color-royal-amethyst) 18%, transparent);
stroke: var(--color-royal-amethyst);
fill: color-mix(in srgb, var(--draw-accent) 18%, transparent);
stroke: var(--draw-accent);
cursor: ew-resize;
}
@@ -117,7 +117,7 @@
.b06-chart__toe-hit:hover,
.b06-chart__toe-hit.is-active {
fill: var(--color-royal-amethyst);
fill: var(--draw-accent);
stroke: transparent;
cursor: pointer;
}
@@ -125,7 +125,7 @@
/* 암 경계선 고름(370-3) — 점선 위 투명 판정 선 · 고르면 경고색 띠 · 세로 끌기. */
.b06-chart__design-hit.b06-chart__rock-hit:hover,
.b06-chart__design-hit.b06-chart__rock-hit.is-active {
stroke: color-mix(in srgb, var(--color-warning) 40%, transparent);
stroke: color-mix(in srgb, var(--draw-warning) 40%, transparent);
}
.b06-chart__design-hit.b06-chart__rock-hit.is-active {
@@ -134,21 +134,21 @@
/* 차도·노견 경계 짧은 수직 틱(N-4-2) — 설계선과 같은 계열, 얇게. */
.b06-chart__carriageway-tick {
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
stroke-width: 1.2;
}
/* 노견 바깥 끝 틱(2026-09-12) — 차도 틱보다 짧고 옅다. 차도 틱과 이 틱 사이가 노견이라,
노면이 넓어졌을 때 차도가 늘었는지 노견이 늘었는지 눈으로 바로 갈린다. */
.b06-chart__shoulder-tick {
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
stroke-width: 1;
opacity: 0.55;
}
/* 노폭 라벨(2026-09-06) — 차도 위 가운데. 확폭이 걸린 측점을 눈으로 가려내는 표기다. */
.b06-chart__carriageway-label {
fill: var(--color-royal-amethyst);
fill: var(--draw-accent);
font-size: 11px;
text-anchor: middle;
pointer-events: none;
@@ -157,7 +157,7 @@
/* 암 경계선(설계선 복사 + 오프셋): 리핑암·발파암 구간 점선 */
.b06-chart__rock-boundary {
fill: none;
stroke: var(--color-warning);
stroke: var(--draw-warning);
stroke-width: 1.6;
stroke-dasharray: 6 4;
stroke-linejoin: round;
@@ -166,31 +166,31 @@
/* 포장층 박스: 노면 양 끝점 기준 두께만큼 하향 채움 */
.b06-chart__pavement {
fill: color-mix(in srgb, var(--color-text-secondary) 30%, transparent);
stroke: var(--color-text-secondary);
fill: color-mix(in srgb, var(--draw-text-secondary) 30%, transparent);
stroke: var(--draw-text-secondary);
stroke-width: 1;
}
/* 고른 노면공의 포장층(152-5) — 범위 카드마다 · 무늬 채움은 inline 이라 무늬 없는 층만 채움이 바뀌고 테두리로 강조 */
.b06-chart__pavement.is-active {
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 30%, transparent);
stroke: var(--draw-danger);
stroke-width: 2.2;
}
/* 노면공 포장사양 무늬(88): 포장 박스 채움 = 바탕 + 무늬 — 콘크리트 빗금 · 아스콘 점 · 쇄석 돌 */
.b06-chart__pave-bg {
fill: color-mix(in srgb, var(--color-text-secondary) 30%, transparent);
fill: color-mix(in srgb, var(--draw-text-secondary) 30%, transparent);
}
.b06-chart__pave-mark {
fill: none;
stroke: color-mix(in srgb, var(--color-text-body) 70%, transparent);
stroke: color-mix(in srgb, var(--draw-text-body) 70%, transparent);
stroke-width: 1;
}
.b06-chart__pave-mark--dot {
fill: color-mix(in srgb, var(--color-text-body) 70%, transparent);
fill: color-mix(in srgb, var(--draw-text-body) 70%, transparent);
stroke: none;
}
@@ -214,13 +214,13 @@
/* 물넘이포장(287-2 · 287-4): 노면공 포장층(노견까지) + 월류 높이 점선 */
.b06-chart__ford-deck-plan {
fill: none;
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1;
stroke-dasharray: 5 4;
}
.b06-chart__ford-pavement {
stroke: var(--color-text-body);
stroke: var(--draw-text-body);
stroke-width: 1;
}
@@ -303,14 +303,14 @@
}
.b06-chart__culvert-revet.is-active {
fill: color-mix(in srgb, var(--color-danger) 45%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 45%, transparent);
stroke: var(--draw-danger);
stroke-width: 2.2;
}
/* 흙막이 · 옹벽 덮개(66-5) — 벽 안으로 지나가는 다른 선을 바탕색으로 가림. */
.b06-chart__wall-cover {
fill: var(--color-surface-raised);
fill: var(--draw-surface-raised);
stroke: none;
pointer-events: none;
}
@@ -333,15 +333,15 @@
(2026-08-30 사용자: 선택해도 하이라이트가 없었다). 색은 기슭막이·집수정 강조와
같은 계열이되, 면이 넓어 채도를 낮춘다. */
.b06-chart__box-hit.is-active {
fill: color-mix(in srgb, var(--color-danger) 16%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 16%, transparent);
stroke: var(--draw-danger);
stroke-width: 1.6;
stroke-dasharray: 4 3;
}
.b06-chart__culvert-apron {
fill: color-mix(in srgb, var(--color-text-secondary) 22%, transparent);
stroke: var(--color-text-secondary);
fill: color-mix(in srgb, var(--draw-text-secondary) 22%, transparent);
stroke: var(--draw-text-secondary);
stroke-width: 1;
stroke-dasharray: 3 2; /* 지반 아래 매설부 — 점선으로 구분 */
}
@@ -349,7 +349,7 @@
.b06-chart__culvert-label {
font-size: 9px;
font-weight: var(--font-weight-medium);
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
}
/* 관 정보 글(관매설 · 세월교 · 267-1) — 노폭 라벨(`.b06-chart__carriageway-label`)과 같은 글 크기 */
@@ -359,7 +359,7 @@
/* 유입 보호공 공간 확보 절토선 — 말단에서 표준단면 절토경사로 지반까지 */
.b06-chart__culvert-cut {
stroke: var(--color-warning);
stroke: var(--draw-warning);
stroke-width: 1.4;
stroke-dasharray: 5 3;
}
@@ -374,7 +374,7 @@
/* 옹벽 물빼기 파이프 단면(73-2) — 두 줄 평행선 · 밝음 · 어두움 토큰을 따름. */
.b06-chart__wall-drain line {
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1.2;
}
@@ -388,8 +388,8 @@
/* 집수정 선택 강조(2026-08-22 사용자) — 기슭막이와 같은 규칙. */
.b06-chart__culvert-basin.is-active {
fill: color-mix(in srgb, var(--color-danger) 45%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 45%, transparent);
stroke: var(--draw-danger);
stroke-width: 2.2;
}
@@ -404,8 +404,8 @@
/* 도수로 · 돌붙임 띠 고름(152-4) — 관 고름과 같은 강조 · 판정면은 굵은 투명 테두리로 여유 */
.b06-chart__culvert-pitching.is-active {
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 30%, transparent);
stroke: var(--draw-danger);
stroke-width: 2.2;
stroke-dasharray: none;
}
@@ -452,8 +452,8 @@
/* 세월교 구체(2026-08-25) — 바닥판은 콘크리트. 측벽은 ㄴ형 집수정 형상을 그대로
쓰므로 `.b06-chart__culvert-basin` 색을 공유한다. */
.b06-chart__ford-slab {
fill: color-mix(in srgb, var(--color-text-secondary) 30%, transparent);
stroke: var(--color-text-secondary);
fill: color-mix(in srgb, var(--draw-text-secondary) 30%, transparent);
stroke: var(--draw-text-secondary);
stroke-width: 1.2;
stroke-linejoin: round;
}
@@ -461,15 +461,15 @@
/* 세월교 본체(바닥판 · 관) 선택 강조(66-1) — 측벽 강조와 같은 색. */
.b06-chart__ford-slab.is-active,
.b06-chart__culvert-pipe.is-active {
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 30%, transparent);
stroke: var(--draw-danger);
stroke-width: 2.2;
}
/* BOX암거 내공(유로) — 면은 비우고 윤곽만. 물이 지나는 빈 공간이라 채우지 않는다. */
.b06-chart__box-barrel {
fill: none;
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1;
stroke-dasharray: 4 3;
stroke-linejoin: round;
@@ -1,6 +1,6 @@
/* SVG 차트 색상은 테마 토큰만 사용한다. */
.b06-chart__bg {
fill: var(--color-surface-raised);
fill: var(--draw-surface-raised);
}
/* 세로 창을 변환으로 옮기는 겹(2026-09-04) — 세로로 눌리거나 늘어나도 선 굵기·점선 간격은
@@ -10,19 +10,19 @@
}
.b06-chart__grid {
stroke: var(--color-border);
stroke: var(--draw-grid);
stroke-width: 1;
}
.b06-chart__axis {
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1;
}
.b06-chart__tick,
.b06-chart__station-label,
.b06-chart__axis-label {
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
font-family: var(--font-body);
}
@@ -35,7 +35,7 @@
.b06-chart__station-label--mc {
font-size: 12px;
font-weight: var(--font-weight-bold);
fill: var(--color-warning);
fill: var(--draw-warning);
}
.b06-station--mc {
@@ -47,7 +47,7 @@
/* 중심고 Y축 라벨(318-2) — 눈금과 같은 10px · 둥근 상자 · 축선 앞에서 멈춤 ·
바탕 = 불투명 섞음(겹친 눈금 글을 위에서 가림 · 319-1) · 색 = 원지반선 · 설계선(379-3 · `--b06-tag` = _Cross_Areas.css) */
.b06-chart__center-box {
fill: color-mix(in srgb, var(--b06-tag) 14%, var(--color-surface-raised));
fill: color-mix(in srgb, var(--b06-tag) 14%, var(--draw-surface-raised));
stroke: var(--b06-tag);
stroke-width: 1;
}
@@ -60,7 +60,7 @@
/* 겹쳐 위 단으로 올린 측점 이름(407-4) — 선 · 지반선 위라 바탕색 테두리로 읽히게. */
.b06-chart__station-label--lifted {
paint-order: stroke;
stroke: var(--color-surface-raised);
stroke: var(--draw-surface-raised);
stroke-width: 3px;
stroke-linejoin: round;
pointer-events: none;
@@ -74,14 +74,14 @@
}
.b06-chart__axis-label {
fill: var(--color-text-body);
fill: var(--draw-text-body);
font-size: var(--text-caption);
font-weight: var(--font-weight-medium);
}
.b06-chart__profile {
fill: none;
stroke: var(--color-chart-0);
stroke: var(--draw-chart-0);
stroke-width: 2.5;
}
@@ -93,14 +93,14 @@
.b06-chart__cross-profile {
fill: none;
stroke: var(--color-chart-1);
stroke: var(--draw-chart-1);
stroke-width: 2.4;
}
/* 종단 계획선(시공계획고): 지반선과 구분되도록 파선 + 강조색 */
.b06-chart__design-profile {
fill: none;
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
stroke-width: 2.2;
stroke-dasharray: 8 4;
}
@@ -119,7 +119,7 @@
}
.b06-chart__balance-boundary {
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
stroke-width: 1.2;
stroke-dasharray: 3 3;
opacity: 0.55;
@@ -148,42 +148,42 @@
.b06-chart__station-line--bp,
.b06-chart__station-line--regular,
.b06-chart__station-line--curve {
stroke: var(--color-warning);
stroke: var(--draw-warning);
}
.b06-chart__station-line--ep {
stroke: var(--color-accent);
stroke: var(--draw-accent);
stroke-dasharray: 4 3;
}
/* 비정규 측점(구조물)은 종단면도에서도 자수정색 파선으로 구분한다(B05 종단 규칙과 정합).
미선택 시 stroke 미정의로 세로선이 안 그려지던 문제 해소. */
.b06-chart__station-line--irregular {
stroke: var(--color-royal-amethyst, rgb(139 92 246));
stroke: var(--draw-accent);
stroke-width: 1.4;
stroke-dasharray: 5 3;
}
/* 추가 측점(368-3) = 일반측점 색 점선 */
.b06-chart__station-line--plus {
stroke: var(--color-warning);
stroke: var(--draw-warning);
stroke-dasharray: 5 3;
}
.b06-chart__station-line--selected,
.b06-chart__center-marker {
stroke: var(--color-danger);
stroke: var(--draw-danger);
stroke-width: 2.5;
}
/* 십자선이 계획 노선(계획고) 위치를 가리킬 때: 종단 계획선과 동일 색으로 대응시킨다 */
.b06-chart__center-marker--design {
stroke: var(--color-royal-amethyst);
stroke: var(--draw-accent);
}
.b06-chart__station--selected .b06-chart__station-label,
.b06-chart__station-label--selected {
fill: var(--color-danger);
fill: var(--draw-danger);
font-weight: var(--font-weight-bold);
}
@@ -122,7 +122,7 @@
.b06-detail__pick-outline path {
fill: none;
stroke: var(--color-danger);
stroke: var(--draw-danger);
stroke-width: 2;
stroke-linecap: square;
}
@@ -144,19 +144,19 @@
/* 그림 강조 한 벌 = 붉은 계통(164-2 · 카드 색은 그대로) — 띠 · 판정 선 · 중심 십자 · 사토장 */
.b06-detail__cad .b06-chart__area.is-active > polygon {
fill: color-mix(in srgb, var(--color-danger) 35%, transparent);
stroke: var(--color-danger);
fill: color-mix(in srgb, var(--draw-danger) 35%, transparent);
stroke: var(--draw-danger);
stroke-width: 2;
}
.b06-detail__cad .b06-chart__design-hit.is-active {
stroke: color-mix(in srgb, var(--color-danger) 70%, transparent);
stroke: color-mix(in srgb, var(--draw-danger) 70%, transparent);
stroke-width: 9;
}
.b06-detail__cad .b06-chart__ground-center.is-active line,
.b06-detail__cad .b06-chart__center.is-active line {
stroke: var(--color-danger);
stroke: var(--draw-danger);
stroke-width: 3;
}
@@ -206,11 +206,11 @@
/* 구조물 선 묶음(295-3) — 나뉜 조각 위 한 줄 · 걸치면 선 하나로 비침 */
.b06-detail__piece:hover,
.b06-detail__piece-group:hover {
stroke: color-mix(in srgb, var(--color-accent) 25%, transparent);
stroke: color-mix(in srgb, var(--draw-accent) 25%, transparent);
}
.b06-detail__piece.is-selected {
stroke: color-mix(in srgb, var(--color-danger) 55%, transparent);
stroke: color-mix(in srgb, var(--draw-danger) 55%, transparent);
stroke-width: 6;
}
@@ -223,11 +223,11 @@
}
.b06-detail__face:hover {
fill: color-mix(in srgb, var(--color-accent) 8%, transparent);
fill: color-mix(in srgb, var(--draw-accent) 8%, transparent);
}
.b06-detail__face.is-selected {
fill: color-mix(in srgb, var(--color-danger) 22%, transparent);
fill: color-mix(in srgb, var(--draw-danger) 22%, transparent);
}
/* 고른 것 값(카드 179-1 꼴) · [칸에 넣기] — [+ − ⤢] 왼쪽 */
+1 -1
View File
@@ -28,7 +28,7 @@
--cad-text-muted: var(--color-text-muted);
--cad-accent: var(--color-accent);
--cad-hover: var(--color-mist-violet);
--cad-canvas: var(--color-canvas);
--cad-canvas: var(--draw-canvas);
}
* {
@@ -339,7 +339,7 @@ export class ScreenCanvasDrawController implements DrawController {
this.context.fillStyle = themeColor('--cad-canvas', CANVAS_BACKGROUND_FALLBACK);
this.context.fillRect(0, 0, this.canvasSize?.x, this.canvasSize?.y);
if (getGridEnabled()) {
this.context.strokeStyle = themeColor('--color-mist', '#242b35');
this.context.strokeStyle = themeColor('--draw-grid', '#242b35');
this.context.lineWidth = 1;
this.context.setLineDash([]);
this.context.beginPath();
+4 -4
View File
@@ -179,22 +179,22 @@
.b08-hydro__pipe text {
font-size: var(--text-caption);
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
}
.b08-hydro__water {
fill: color-mix(in srgb, var(--color-chart-0) 30%, transparent);
fill: color-mix(in srgb, var(--draw-chart-0) 30%, transparent);
}
.b08-hydro__wall {
fill: none;
stroke: var(--color-text-body);
stroke: var(--draw-text-body);
stroke-width: 2;
}
.b08-hydro__line {
fill: none;
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1;
}
+20 -20
View File
@@ -81,41 +81,41 @@
}
.b08-milestone__line {
stroke: var(--color-text-secondary);
stroke: var(--draw-text-secondary);
stroke-width: 1.5;
}
.b08-milestone__box rect {
fill: var(--color-surface);
stroke: var(--color-text-secondary);
fill: var(--draw-surface);
stroke: var(--draw-text-secondary);
stroke-width: 1.2;
}
.b08-milestone__box text {
fill: var(--color-text);
fill: var(--draw-text);
}
/* 153 상자 색 — 공사지(시점 · 중점) = 강조색 · 기준점 = 경고색 · 거래처 = 성공색 · 옅은 채움 + 테두리(테마 변수 · 밝음 · 어두움 같이) */
.b08-milestone__box.is-site rect {
fill: color-mix(in srgb, var(--color-accent) 7%, var(--color-surface));
stroke: var(--color-accent);
fill: color-mix(in srgb, var(--draw-accent) 7%, var(--draw-surface));
stroke: var(--draw-accent);
stroke-dasharray: 4 3;
}
.b08-milestone__box.is-start rect,
.b08-milestone__box.is-mid rect {
fill: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
stroke: var(--color-accent);
fill: color-mix(in srgb, var(--draw-accent) 16%, var(--draw-surface));
stroke: var(--draw-accent);
}
.b08-milestone__box.is-base rect {
fill: color-mix(in srgb, var(--color-warning) 18%, var(--color-surface));
stroke: var(--color-warning);
fill: color-mix(in srgb, var(--draw-warning) 18%, var(--draw-surface));
stroke: var(--draw-warning);
}
.b08-milestone__box.is-route rect {
fill: color-mix(in srgb, var(--color-success) 14%, var(--color-surface));
stroke: var(--color-success);
fill: color-mix(in srgb, var(--draw-success) 14%, var(--draw-surface));
stroke: var(--draw-success);
}
.b08-milestone__box.is-route,
@@ -125,15 +125,15 @@
.b08-milestone__box.is-route:hover rect,
.b08-milestone__box.is-route:focus-visible rect {
stroke: var(--color-accent);
stroke: var(--draw-accent);
stroke-width: 2;
}
.b08-milestone__km {
fill: var(--color-text);
fill: var(--draw-text);
font-size: 12px;
paint-order: stroke;
stroke: var(--color-surface);
stroke: var(--draw-surface);
stroke-width: 4px;
stroke-linejoin: round;
}
@@ -173,13 +173,13 @@
/* 거래처 상자 윗줄 = 항목(244-6) · 글 크기 = 거래처 이름(254) */
.b08-milestone__items {
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
font-size: 13px;
}
/* 상자 작은 글씨 = 주소 · 좌표(244-12) */
.b08-milestone__note {
fill: var(--color-text-secondary);
fill: var(--draw-text-secondary);
font-size: 10px;
}
@@ -228,8 +228,8 @@
/* 154 연결 점 ● — 거래처 · 기준점 상자 왼쪽 위 안쪽(254) · 끌어 기준점 · 공사지에 놓으면 연결 */
.b08-milestone__connect {
fill: var(--color-success);
stroke: var(--color-surface);
fill: var(--draw-success);
stroke: var(--draw-surface);
stroke-width: 2;
cursor: crosshair;
opacity: 0.85;
@@ -240,7 +240,7 @@
}
.b08-milestone__wire {
stroke: var(--color-success);
stroke: var(--draw-success);
stroke-width: 2;
stroke-dasharray: 6 4;
pointer-events: none;
+1 -1
View File
@@ -248,7 +248,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
context.beginPath();
context.arc(at[0], at[1], 7, 0, Math.PI * 2);
context.lineWidth = 2.5;
context.strokeStyle = themeColor("--color-primary", "#6d28d9");
context.strokeStyle = themeColor("--draw-primary", "#6d28d9");
context.stroke();
}
}
+3 -3
View File
@@ -105,9 +105,9 @@ def test_모달_배선_단추_칸_비율():
def test_강조_붉은_한_벌_고름_hover_다름_빈칸():
css = _read("B06_Section_UI_Style_Cross_Detail.css")
for selector in (
".b06-detail__cad .b06-chart__area.is-active > polygon {\n fill: color-mix(in srgb, var(--color-danger)",
".b06-detail__cad .b06-chart__design-hit.is-active {\n stroke: color-mix(in srgb, var(--color-danger)",
".b06-detail__cad .b06-chart__ground-center.is-active line,\n.b06-detail__cad .b06-chart__center.is-active line {\n stroke: var(--color-danger);",
".b06-detail__cad .b06-chart__area.is-active > polygon {\n fill: color-mix(in srgb, var(--draw-danger)",
".b06-detail__cad .b06-chart__design-hit.is-active {\n stroke: color-mix(in srgb, var(--draw-danger)",
".b06-detail__cad .b06-chart__ground-center.is-active line,\n.b06-detail__cad .b06-chart__center.is-active line {\n stroke: var(--draw-danger);",
".b06-detail__qtable .is-picked {\n background: color-mix(in srgb, var(--color-danger) 18%",
".b06-detail__qtable [data-key][data-empty] {\n cursor: default;",
):
+7 -7
View File
@@ -180,14 +180,14 @@ def _css_rule(css: str, selector: str) -> str:
def test_자유지정_녹색_넘음_빨강_띠_보라():
# 367-4 선 · 미리 보기 · 점 = --color-success · 넘음 빨강 · 범위 띠 · 경계 보라 그대로
# 367-4 선 · 미리 보기 · 점 = --draw-success · 넘음 빨강 · 범위 띠 · 경계 보라 그대로
css = _read("B05_Profile/B05_Profile_UI_Style_Table.css")
assert "stroke: var(--color-success);" in _css_rule(css, ".b05-profile-freemark__line")
assert "stroke: var(--color-success);" in _css_rule(css, ".b05-profile-freemark__dot")
assert "var(--color-danger" in _css_rule(css, ".b05-profile-freemark__line.is-over")
assert "var(--color-danger" in _css_rule(css, ".b05-profile-freemark__grade.is-over")
assert "--color-primary" in _css_rule(css, ".b05-profile-freemark__band")
assert "--color-primary" in _css_rule(css, ".b05-profile-freemark__edge")
assert "stroke: var(--draw-success);" in _css_rule(css, ".b05-profile-freemark__line")
assert "stroke: var(--draw-success);" in _css_rule(css, ".b05-profile-freemark__dot")
assert "var(--draw-danger" in _css_rule(css, ".b05-profile-freemark__line.is-over")
assert "var(--draw-danger" in _css_rule(css, ".b05-profile-freemark__grade.is-over")
assert "--draw-primary" in _css_rule(css, ".b05-profile-freemark__band")
assert "--draw-primary" in _css_rule(css, ".b05-profile-freemark__edge")
# 밝음 · 어두움(명시 · OS 자동) 모두 success · danger 토큰 있음
theme = _read("ui_template/ui_template_theme.css")
auto = theme[theme.index("@media (prefers-color-scheme: dark)") :]
@@ -84,9 +84,9 @@ def test_153_지도_보기_유지_상자_색_끌어_옮김():
) # 처음 한 번만 맞춤 · 그 뒤 줌 그대로
css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css")
for kind, var in (
("is-site", "--color-accent"),
("is-base", "--color-warning"),
("is-route", "--color-success"),
("is-site", "--draw-accent"),
("is-base", "--draw-warning"),
("is-route", "--draw-success"),
):
block = css[css.index(f".b08-milestone__box.{kind} rect {{") :]
assert var in block[: block.index("}")], kind
+1 -1
View File
@@ -186,7 +186,7 @@ def test_배선():
css = _read("B05_Profile/B05_Profile_UI_Style_Table.css")
rule = css[css.index(".b05-profile-freemark__span {") :]
rule = rule[: rule.index("}")]
assert "--color-primary" in rule and "--color-surface" in rule # 밝음 · 어두움 토큰
assert "--draw-primary" in rule and "--draw-surface" in rule # 밝음 · 어두움 토큰
def test_줄_수():
+3 -3
View File
@@ -159,7 +159,7 @@ def test_two_crosshairs(result: dict) -> None:
assert cross["design_only_ground"] is None # 설계 없음 = 십자 하나(지반 폴백)
assert cross["scaled"] == cross["want_scaled"] # 둘 다 자기 중심에서 배율 되돌림
areas = _src("B06_Section", "B06_Section_UI_Style_Cross_Areas.css")
assert "var(--color-chart-1)" in _rule(areas, ".b06-chart__ground-center-marker")
assert "var(--draw-chart-1)" in _rule(areas, ".b06-chart__ground-center-marker")
def test_draft_detail_profile_is_draft(result: dict) -> None:
@@ -192,8 +192,8 @@ def test_css_layout() -> None:
box = _rule(css, ".b06-chart__center-box")
assert "var(--b06-tag)" in box # 색 = 갈래 변수(379-3)
areas = _src("B06_Section", "B06_Section_UI_Style_Cross_Areas.css")
assert "var(--color-chart-1)" in _rule(areas, ".b06-chart__center-tag--ground") # 원지반선
assert "var(--color-royal-amethyst)" in _rule(areas, ".b06-chart__center-tag--design") # 설계선
assert "var(--draw-chart-1)" in _rule(areas, ".b06-chart__center-tag--ground") # 원지반선
assert "var(--draw-accent)" in _rule(areas, ".b06-chart__center-tag--design") # 설계선
assert "font-size" not in _rule(css, ".b06-chart__center-text") # 글자 크기 = 눈금 그대로
+10 -4
View File
@@ -65,11 +65,17 @@ def test_b08_b09_earthwork_station_mc() -> None:
def test_b05_b06_screen_mc_emphasis() -> None:
css = _src("B06_Section/B06_Section_UI_Style_Cross.css") + _src("B06_Section/B06_Section_UI_Style_Cross_Chart.css")
css = _src("B06_Section/B06_Section_UI_Style_Cross.css") + _src(
"B06_Section/B06_Section_UI_Style_Cross_Chart.css"
)
assert "header strong.b06-station--mc {" in css # 카드 머리 strong 규칙을 이김
for rule in ("\n.b06-chart__station-label--mc {", "\n.b06-station--mc {"):
# 그림 글 = 그림 전용 값(551-2) · 카드 머리 글 = 화면 경고색
for rule, warn in (
("\n.b06-chart__station-label--mc {", "var(--draw-warning)"),
("\n.b06-station--mc {", "var(--color-warning)"),
):
body = css[css.index(rule) : css.index("}", css.index(rule))]
assert "var(--color-warning)" in body
assert warn in body
assert "var(--font-weight-bold)" in body
# 고름 빨강이 MC 색을 이김 — 뒤에 섬
assert css.index(".b06-chart__station-label--mc {") < css.index(
@@ -84,7 +90,7 @@ def test_b05_b06_screen_mc_emphasis() -> None:
assert '${mc ? " b06-station--mc" : ""}' in cross
ticks = _src("B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts")
assert 'mc ? "bold 13px' in ticks
assert 'themeColor("--color-warning"' in ticks
assert 'themeColor("--draw-warning"' in ticks
swap = _src("B06_Section/logic/B06_Section_Station_Swap.ts")
assert 'formatStation(station.chainageM) + (kind === "curve" ? " (MC)" : "")' in swap
+155
View File
@@ -0,0 +1,155 @@
"""551-2 그림 전용 값(--draw-*) 지킴 — 화면 테마 변수(--color-*)를 바꿔도 그림 색은 그대로.
- `ui_template_theme.css` 끝 블록의 --draw-* 는 밝음 · 어두움 · 시스템 어두움 모두 글자 hex.
- 그림 CSS(B05 · B06 · B08 SVG)의 stroke · fill 은 --color-* 를 쓰지 않음.
- 지도 · 3D · CAD 캔버스가 읽는 바탕 · 선 색은 --draw-*.
- 옛 변수 값을 일부러 바꿔도 그림 선언의 풀린 색이 같음(밝음 · 어두움 · 시스템 어두움).
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
THEME = ROOT / "ui_template" / "ui_template_theme.css"
DRAW_CSS = [
"B05_Profile/B05_Profile_UI_Style.css",
"B05_Profile/B05_Profile_UI_Style_RouteEdit.css",
"B05_Profile/B05_Profile_UI_Style_RouteRecommend.css",
"B05_Profile/B05_Profile_UI_Style_Table.css",
"B06_Section/B06_Section_UI_Style.css",
"B06_Section/B06_Section_UI_Style_Cross_Areas.css",
"B06_Section/B06_Section_UI_Style_Cross_Chart.css",
"B06_Section/B06_Section_UI_Style_Cross_Detail.css",
"B08_Quantity/B08_Quantity_UI_Hydro.css",
"B08_Quantity/B08_Quantity_UI_Milestone.css",
]
# 캔버스 · 3D 가 읽는 그림 색 — (파일, 꼭 있어야 할 글)
DRAW_READS = [
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-surface"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-text-secondary"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-danger"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-primary"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-text-secondary"'),
("B05_Profile/B05_Profile_UI_Viewer.ts", '"--draw-surface-raised"'),
("B04_PreProcess/B04_PreProcess_UI_Camera.ts", '"--draw-surface-raised"'),
("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts", '"--draw-primary"'),
("B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts", '"--draw-warning"'),
("B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts", '"--draw-canvas"'),
("B07_DesignDetail/openwebcad/src/App.css", "--cad-canvas: var(--draw-canvas);"),
(
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
"themeColor('--draw-grid'",
),
]
NO_COLOR_READS = [
"B05_Profile/B05_Profile_UI_RouteEdit_Render.ts",
"B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts",
"B05_Profile/B05_Profile_UI_Viewer.ts",
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
]
DECL = re.compile(r"^\s*(stroke|fill|stop-color|--b06-tag)\s*:(.*);", re.M)
VAR = re.compile(r"var\((--[a-z0-9-]+)(?:\s*,\s*([^()]*(?:\([^()]*\))?[^()]*))?\)")
HEX = re.compile(r"#[0-9a-f]{6}")
def _blocks(css: str) -> dict[str, dict[str, str]]:
"""밝음 · 어두움 · 시스템 어두움 블록의 --변수 정의(뒤 블록이 앞을 덮음)."""
css = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
out: dict[str, dict[str, str]] = {"light": {}, "dark": {}, "auto": {}}
def walk(text: str, media: bool) -> None:
i = 0
while True:
open_at = text.find("{", i)
if open_at < 0:
return
head = text[i:open_at].strip().split("}")[-1].strip()
depth, j = 1, open_at + 1
while depth:
depth += {"{": 1, "}": -1}.get(text[j], 0)
j += 1
body = text[open_at + 1 : j - 1]
if head.startswith("@media (prefers-color-scheme: dark)"):
walk(body, True)
else:
mode = None
if head == ":root":
mode = "light"
elif head == ':root[data-theme="dark"]':
mode = "dark"
elif media and head == ':root:not([data-theme="light"])':
mode = "auto"
if mode:
for name, value in re.findall(r"(--[a-z0-9-]+)\s*:([^;]*);", body):
out[mode][name] = " ".join(value.split())
i = j
walk(css, False)
return out
def _modes(css: str) -> dict[str, dict[str, str]]:
b = _blocks(css)
return {
"light": b["light"],
"dark": {**b["light"], **b["dark"]},
"auto": {**b["light"], **b["auto"]},
}
def resolve(value: str, env: dict[str, str]) -> str:
for _ in range(10):
new = VAR.sub(lambda m: env.get(m.group(1), (m.group(2) or "").strip()), value)
if new == value:
break
value = new
return " ".join(value.split()).lower()
def draw_decls() -> list[tuple[str, str]]:
out = []
for rel in DRAW_CSS:
text = re.sub(r"/\*.*?\*/", "", (ROOT / rel).read_text(encoding="utf-8"), flags=re.S)
out += [(rel, m.group(2).strip()) for m in DECL.finditer(text)]
return out
def test_draw_values_are_literal_hex():
blocks = _blocks(THEME.read_text(encoding="utf-8"))
for mode, env in blocks.items():
draws = {k: v for k, v in env.items() if k.startswith("--draw-")}
assert draws, mode
for name, value in draws.items():
assert HEX.fullmatch(value), (mode, name, value)
assert len([k for k in blocks["light"] if k.startswith("--draw-")]) == 17
def test_drawing_css_uses_no_screen_tokens():
decls = draw_decls()
assert len(decls) > 100
bad = [(rel, v) for rel, v in decls if "var(--color-" in v]
assert not bad, bad
def test_canvas_and_3d_read_draw_tokens():
for rel, needle in DRAW_READS:
assert needle in (ROOT / rel).read_text(encoding="utf-8"), (rel, needle)
for rel in NO_COLOR_READS:
text = (ROOT / rel).read_text(encoding="utf-8")
assert not re.search(r"""(getPropertyValue|themeColor)\(\s*["']--color-""", text), rel
def test_changing_screen_tokens_keeps_drawing_colors():
css = THEME.read_text(encoding="utf-8")
# 옛 화면 변수를 모두 초록으로 바꾼 판 — sub1 이 테마를 갈아도 그림이 안 따라가는지
swapped = re.sub(r"(--color-[a-z0-9-]+\s*:)[^;]*;", r"\1 #00ff00;", css)
before, after = _modes(css), _modes(swapped)
decls = draw_decls()
for mode in ("light", "dark", "auto"):
for rel, value in decls:
a = resolve(value, before[mode])
assert "var(" not in a, (mode, rel, value)
assert a == resolve(value, after[mode]), (mode, rel, value)
+1 -1
View File
@@ -132,4 +132,4 @@ def test_옹벽_표식은_구조물_종류로() -> None:
"retaining_wall"
]
css = (B06 / "B06_Section_UI_Style_Cross_Areas.css").read_text(encoding="utf-8")
assert re.search(r"\.b06-chart__wall-drain line \{[^}]*var\(--color-", css), "색 토큰 없음"
assert re.search(r"\.b06-chart__wall-drain line \{[^}]*var\(--draw-", css), "색 토큰 없음"
+58
View File
@@ -562,3 +562,61 @@ html.is-bm-route .m01lab-src__body * {
user-select: text;
-webkit-user-select: text;
}
/* =============================================================================
* 551-2 그림 전용 값 — 화면 테마와 따로 · 지금 색 고정
* 종단 · 횡단 · 이정표 SVG 선 · 지도 계획선 · CAD 바탕 · 3D 바탕은 --draw-* 만 씀.
* 값은 글자 hex 로 박음(--color-* 를 가리키지 않음) — 화면 테마가 바뀌어도 그림 색은 그대로.
* ========================================================================== */
:root {
--draw-accent: #3e0079; /* 설계선 · EP 선 · 이정표 강조 */
--draw-primary: #26114a; /* 자유 표시 · 노선 추천 계획선 */
--draw-chart-0: #5b8def; /* 원지반선 · 성토 */
--draw-chart-1: #34c38f; /* 횡단 원지반선 */
--draw-chart-2: #f1963b; /* 토사 깎기 */
--draw-chart-3: #9b6dde; /* 발파암 깎기 */
--draw-success: #1f8a4c;
--draw-warning: #b8860b;
--draw-danger: #c0392b;
--draw-text: #26114a;
--draw-text-body: #333333;
--draw-text-secondary: #615e6e;
--draw-text-muted: #9491a1;
--draw-grid: #e6e2e3; /* 눈금 · CAD 격자 */
--draw-surface: #f6f7fa;
--draw-surface-raised: #ffffff; /* 그림 바탕 · 3D 바탕 */
--draw-canvas: #ffffff; /* CAD 바탕 */
}
:root[data-theme="dark"] {
--draw-accent: #b99aff;
--draw-primary: #b99aff;
--draw-success: #3fbb6f;
--draw-warning: #e0b64a;
--draw-danger: #e8695a;
--draw-text: #d8ccff;
--draw-text-body: #e4e0f0;
--draw-text-secondary: #b3aec6;
--draw-text-muted: #8b86a0;
--draw-grid: #322a45;
--draw-surface: #1e1930;
--draw-surface-raised: #251f38;
--draw-canvas: #16121f;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--draw-accent: #b99aff;
--draw-primary: #b99aff;
--draw-success: #3fbb6f;
--draw-danger: #e8695a;
--draw-text: #d8ccff;
--draw-text-body: #e4e0f0;
--draw-text-secondary: #b3aec6;
--draw-text-muted: #8b86a0;
--draw-grid: #322a45;
--draw-surface: #1e1930;
--draw-surface-raised: #251f38;
--draw-canvas: #16121f;
}
}