feat(B07): CAD 화면을 앱 테마에 맞춘다 — 디자인 토큰·화이트/블랙 모드

iframe 안 CAD가 다크 색을 하드코딩하고 있어 앱의 화이트/블랙 토글과 따로 놀던
것을 고친다. 색 정의처는 앱과 같은 ui_template_theme.css 하나로 모은다.

- openwebcad가 ui_template_theme.css를 직접 import — App.css의 하드코딩 색
  88개를 var(--color-*)로 전량 치환, CAD 골격용 별칭 토큰만 새로 붙임
- src/theme.ts 신설: 동일 오리진 localStorage(frd_theme) + storage 이벤트로
  부모 토글을 리로드 없이 추종(부모 코드 수정 없음), 시스템 폴백 유지
- 캔버스 배경·그리드는 토큰 경유, 저장된 도면 색은 라이트 모드에서 그릴 때만
  휘도 보정(paintColor) — 저장 데이터는 불변
- 테마 변경 시 bumpSceneVersion()으로 정적 장면 캐시 비트맵 재생성
- Button/LayerManager의 tailwind 색 유틸 제거, index.html의 bg-* 고정 제거

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 16:38:38 +09:00
co-authored by Claude Fable 5
parent 6e195afb69
commit 3aed06eaca
7 changed files with 250 additions and 112 deletions
@@ -1,12 +1,16 @@
import { Point, type Vector } from '@flatten-js/core';
import { CANVAS_BACKGROUND_COLOR, MOUSE_ZOOM_MULTIPLIER } from '../App.consts';
import { MOUSE_ZOOM_MULTIPLIER } from '../App.consts';
import { getAngleWithXAxis } from '../helpers/get-angle-with-x-axis.ts';
import { getBoundingBoxOfMultipleEntities } from '../helpers/get-bounding-box-of-multiple-entities.ts';
import { mapNumberRange } from '../helpers/map-number-range.ts';
import { StateVariable } from '../helpers/undo-stack.ts';
import { getEntities, getGridEnabled, triggerReactUpdate } from '../state.ts';
import { paintColor, themeColor } from '../theme.ts';
import { DEFAULT_TEXT_OPTIONS, type DrawController } from './DrawController';
/** 토큰을 못 읽을 때만 쓰는 캔버스 배경 폴백. */
const CANVAS_BACKGROUND_FALLBACK = '#111';
/**
* Screen coordinate system:
* 0, 0 X
@@ -231,10 +235,12 @@ export class ScreenCanvasDrawController implements DrawController {
public setLineStyles(
isHighlighted: boolean,
isSelected: boolean,
color: string,
rawColor: string,
lineWidth: number,
dash: number[] = []
) {
// 저장된 도면 색은 다크 배경 기준 — 라이트 테마에서는 그릴 때만 대비를 맞춘다.
const color = paintColor(rawColor);
if (this.batching) {
const effectiveWidth = isHighlighted ? lineWidth + 1 : lineWidth;
const effectiveDash = isSelected ? [5, 5] : dash;
@@ -298,7 +304,7 @@ export class ScreenCanvasDrawController implements DrawController {
}
public setFillStyles(fillColor: string) {
this.context.fillStyle = fillColor;
this.context.fillStyle = paintColor(fillColor);
}
/**
@@ -330,10 +336,10 @@ export class ScreenCanvasDrawController implements DrawController {
if (!this.context) return;
if (this.batching) this.flushBatch();
this.context.fillStyle = CANVAS_BACKGROUND_COLOR;
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 = '#242b35';
this.context.strokeStyle = themeColor('--color-mist', '#242b35');
this.context.lineWidth = 1;
this.context.setLineDash([]);
this.context.beginPath();
@@ -554,7 +560,7 @@ export class ScreenCanvasDrawController implements DrawController {
this.context.rotate(angle);
this.context.font = `${opts.fontSize}px ${opts.fontFamily}`;
this.context.textAlign = opts.textAlign;
this.context.fillStyle = opts.textColor;
this.context.fillStyle = paintColor(opts.textColor);
this.context.textBaseline = 'middle';
this.context.fillText(label, 0, 0);
this.context.restore();
@@ -630,7 +636,7 @@ export class ScreenCanvasDrawController implements DrawController {
public fillRectScreen(xMin: number, yMin: number, width: number, height: number, color: string) {
if (this.batching) this.flushBatch();
// TODO see if we need to replace this with a call to fillPolygon
this.context.fillStyle = color;
this.context.fillStyle = paintColor(color);
this.context.fillRect(xMin, this.canvasSize.y - yMin, width, height);
}