diff --git a/B07_DesignDetail/openwebcad/index.html b/B07_DesignDetail/openwebcad/index.html
index 365aed00..4eb332cc 100644
--- a/B07_DesignDetail/openwebcad/index.html
+++ b/B07_DesignDetail/openwebcad/index.html
@@ -7,9 +7,9 @@
Aislo 2D Drawing
-
-
-
+
+
+
diff --git a/B07_DesignDetail/openwebcad/src/App.css b/B07_DesignDetail/openwebcad/src/App.css
index 8dc87e3f..92bf0607 100644
--- a/B07_DesignDetail/openwebcad/src/App.css
+++ b/B07_DesignDetail/openwebcad/src/App.css
@@ -1,4 +1,6 @@
@import "tailwindcss";
+/* 색 토큰의 정의처는 앱과 같은 파일 하나 — 여기서 색을 새로 정하지 않는다. */
+@import "../../../ui_template/ui_template_theme.css";
:root {
--cad-title-height: 42px;
@@ -7,9 +9,21 @@
--cad-command-height: 0px;
--cad-status-height: 28px;
--cad-panel-width: 248px;
- font-family: Inter, Pretendard, "Noto Sans KR", system-ui, sans-serif;
- color: #dce6f2;
- background: #11161d;
+ font-family: var(--font-body);
+ color: var(--color-text-body);
+ background: var(--color-bg);
+
+ /* CAD 표면 단계 — 앱 토큰을 CAD 골격(툴바·리본·패널)에 이름만 붙여 쓴다. */
+ --cad-chrome: var(--color-surface);
+ --cad-chrome-raised: var(--color-surface-raised);
+ --cad-chrome-sunken: var(--color-bg);
+ --cad-line: var(--color-border);
+ --cad-text: var(--color-text-body);
+ --cad-text-dim: var(--color-text-secondary);
+ --cad-text-muted: var(--color-text-muted);
+ --cad-accent: var(--color-accent);
+ --cad-hover: var(--color-mist-violet);
+ --cad-canvas: var(--color-canvas);
}
* {
@@ -56,7 +70,7 @@ body > canvas[data-id="canvas"] {
var(--cad-command-height) -
var(--cad-status-height)
);
- background: #111;
+ background: var(--cad-canvas);
cursor: none;
}
@@ -68,9 +82,9 @@ body > canvas[data-id="canvas"] {
align-items: center;
gap: 24px;
padding: 0 12px;
- background: #19222d;
- border-bottom: 1px solid #344252;
- box-shadow: 0 1px 4px #0008;
+ background: var(--cad-chrome);
+ border-bottom: 1px solid var(--cad-line);
+ box-shadow: var(--shadow-sm);
}
.cad-brand {
display: flex;
@@ -79,12 +93,12 @@ body > canvas[data-id="canvas"] {
min-width: 220px;
}
.cad-brand strong {
- color: #f7fbff;
+ color: var(--color-text);
font-size: 14px;
}
.cad-brand span,
.cad-file-state {
- color: #91a2b5;
+ color: var(--cad-text-dim);
font-size: 11px;
}
.cad-file-state {
@@ -96,8 +110,8 @@ body > canvas[data-id="canvas"] {
width: 7px;
height: 7px;
border-radius: 50%;
- background: #49b675;
- box-shadow: 0 0 0 2px #49b67522;
+ background: var(--color-success);
+ box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-success) 25%, transparent);
}
.cad-title-actions {
display: flex;
@@ -114,8 +128,8 @@ body > canvas[data-id="canvas"] {
font-size: 12px;
}
.cad-title-actions button:hover {
- border-color: #4d6074;
- background: #273544;
+ border-color: var(--cad-line);
+ background: var(--cad-hover);
}
.cad-ribbon {
@@ -128,15 +142,15 @@ body > canvas[data-id="canvas"] {
align-items: stretch;
padding: 5px 8px 3px;
overflow-x: auto;
- background: #202b37;
- border-bottom: 1px solid #3c4a59;
+ background: var(--cad-chrome-raised);
+ border-bottom: 1px solid var(--cad-line);
}
.cad-ribbon-group {
display: flex;
flex-direction: column;
min-width: max-content;
padding: 0 9px;
- border-right: 1px solid #40505f;
+ border-right: 1px solid var(--cad-line);
}
.cad-ribbon-tools {
display: flex;
@@ -145,7 +159,7 @@ body > canvas[data-id="canvas"] {
}
.cad-ribbon-group__label {
margin-top: auto;
- color: #8293a5;
+ color: var(--cad-text-muted);
font-size: 10px;
text-align: center;
}
@@ -159,7 +173,7 @@ body > canvas[data-id="canvas"] {
gap: 3px;
align-items: stretch;
font-size: 10px;
- color: #8293a5;
+ color: var(--cad-text-muted);
}
.cad-prop > span {
text-align: center;
@@ -169,9 +183,9 @@ body > canvas[data-id="canvas"] {
height: 24px;
min-width: 64px;
padding: 0 4px;
- color: #dce6f2;
- background: #2a3745;
- border: 1px solid #40505f;
+ color: var(--cad-text);
+ background: var(--cad-chrome-sunken);
+ border: 1px solid var(--cad-line);
border-radius: 4px;
font-size: 11px;
}
@@ -183,8 +197,8 @@ body > canvas[data-id="canvas"] {
height: 24px;
width: 40px;
padding: 1px;
- background: #2a3745;
- border: 1px solid #40505f;
+ background: var(--cad-chrome-sunken);
+ border: 1px solid var(--cad-line);
border-radius: 4px;
cursor: pointer;
}
@@ -203,24 +217,24 @@ body > canvas[data-id="canvas"] {
}
.cad-tool:hover:not(:disabled),
.cad-tool[data-active="true"] {
- border-color: #4f87b8;
- background: #2b455c;
+ border-color: var(--cad-accent);
+ background: var(--cad-hover);
}
.cad-tool[data-active="true"] {
- box-shadow: inset 0 -2px #4ca6e8;
+ box-shadow: inset 0 -2px var(--cad-accent);
}
.cad-tool:disabled {
- color: #657382;
+ color: var(--color-smoke);
cursor: not-allowed;
}
.cad-tool__glyph {
height: 27px;
- color: #a9d7fb;
+ color: var(--cad-accent);
font-size: 22px;
line-height: 27px;
}
.cad-tool:disabled .cad-tool__glyph {
- color: #657382;
+ color: var(--color-smoke);
}
.cad-inspector {
@@ -230,8 +244,8 @@ body > canvas[data-id="canvas"] {
bottom: calc(var(--cad-command-height) + var(--cad-status-height));
left: 0;
width: var(--cad-panel-width);
- background: #1b2530;
- border-right: 1px solid #3d4c5c;
+ background: var(--cad-chrome);
+ border-right: 1px solid var(--cad-line);
transition: width 120ms ease;
}
.cad-inspector[data-collapsed="true"] {
@@ -244,57 +258,57 @@ body > canvas[data-id="canvas"] {
left: 100%;
width: 20px;
height: 38px;
- border: 1px solid #465769;
+ border: 1px solid var(--cad-line);
border-left: 0;
border-radius: 0 4px 4px 0;
- background: #263442;
- color: #a9bbcd;
+ background: var(--cad-chrome-raised);
+ color: var(--cad-text-dim);
}
.cad-inspector-tabs {
display: grid;
grid-template-columns: 1fr 1fr;
height: 38px;
- border-bottom: 1px solid #394857;
+ border-bottom: 1px solid var(--cad-line);
}
.cad-inspector-tabs button {
border: 0;
border-bottom: 2px solid transparent;
- background: #17202a;
- color: #94a7ba;
+ background: var(--cad-chrome-sunken);
+ color: var(--cad-text-dim);
font-size: 12px;
}
.cad-inspector-tabs button[data-active="true"] {
- border-bottom-color: #4da3df;
- background: #223141;
- color: white;
+ border-bottom-color: var(--cad-accent);
+ background: var(--cad-chrome-raised);
+ color: var(--color-text);
}
.cad-properties {
padding: 14px 12px;
}
.cad-properties h2 {
margin: 0 0 14px;
- color: #f3f7fb;
+ color: var(--color-text);
font-size: 13px;
font-weight: 600;
}
.cad-properties dl {
margin: 0;
- border-top: 1px solid #344352;
+ border-top: 1px solid var(--cad-line);
}
.cad-properties dl div {
display: grid;
grid-template-columns: 92px 1fr;
padding: 8px 0;
- border-bottom: 1px solid #2d3a47;
+ border-bottom: 1px solid var(--cad-line);
font-size: 11px;
}
.cad-properties dt {
- color: #8092a4;
+ color: var(--cad-text-dim);
}
.cad-properties dd {
margin: 0;
overflow: hidden;
- color: #d7e1eb;
+ color: var(--cad-text);
text-overflow: ellipsis;
}
.cad-layer-manager {
@@ -306,7 +320,23 @@ body > canvas[data-id="canvas"] {
min-height: 36px;
padding-top: 6px;
padding-bottom: 6px;
- background: #202c38;
+}
+/* 도면층 버튼 — 색은 토큰으로만, 상태는 Button이 붙이는 data-* 속성으로 가른다. */
+.cad-layer-manager button[data-type="regular"] {
+ background: var(--cad-chrome-sunken);
+ color: var(--cad-accent);
+}
+.cad-layer-manager button[data-type="transparent"] {
+ background: transparent;
+ color: var(--cad-accent);
+}
+.cad-layer-manager button:hover {
+ background: var(--cad-hover);
+ color: var(--color-text);
+}
+.cad-layer-manager button[data-active="true"] {
+ background: var(--color-primary);
+ color: var(--color-primary-text);
}
.cad-view-controls {
@@ -317,29 +347,29 @@ body > canvas[data-id="canvas"] {
display: flex;
align-items: center;
overflow: hidden;
- border: 1px solid #465565;
+ border: 1px solid var(--cad-line);
border-radius: 4px;
- background: #202b37e8;
- box-shadow: 0 3px 12px #0008;
+ background: var(--cad-chrome-raised);
+ box-shadow: var(--shadow-lg);
}
.cad-view-controls button {
width: 34px;
height: 32px;
border: 0;
- border-right: 1px solid #3d4c5b;
+ border-right: 1px solid var(--cad-line);
background: transparent;
font-size: 17px;
}
.cad-view-controls button:hover {
- background: #31516b;
+ background: var(--cad-hover);
}
.cad-view-controls__fit {
- color: #7fd0ff;
+ color: var(--cad-accent);
font-size: 15px;
}
.cad-view-controls span {
min-width: 48px;
- color: #9fb0c1;
+ color: var(--cad-text-dim);
font-size: 10px;
text-align: center;
}
@@ -357,11 +387,11 @@ body > canvas[data-id="canvas"] {
white-space: nowrap;
}
.cad-command-prompt span {
- color: #6eaee0;
+ color: var(--cad-accent);
}
.cad-command-prompt strong {
overflow: hidden;
- color: #aab8c6;
+ color: var(--cad-text-dim);
font-weight: 400;
text-overflow: ellipsis;
}
@@ -372,7 +402,7 @@ body > canvas[data-id="canvas"] {
height: 28px;
}
.cad-command-area label {
- color: #e6eef6;
+ color: var(--color-text);
font-size: 11px;
font-weight: 600;
}
@@ -380,16 +410,16 @@ body > canvas[data-id="canvas"] {
flex: 1;
height: 26px;
padding: 0 8px;
- border: 1px solid #46586a;
+ border: 1px solid var(--cad-line);
border-radius: 2px;
outline: none;
- background: #0e141a;
- color: #eef5fc;
+ background: var(--cad-chrome-sunken);
+ color: var(--cad-text);
font: 11px Consolas, monospace;
}
.cad-command-area input:focus {
- border-color: #4b9bd3;
- box-shadow: 0 0 0 1px #4b9bd344;
+ border-color: var(--color-focus-ring);
+ box-shadow: 0 0 0 1px var(--color-focus-ring);
}
.cad-statusbar {
@@ -403,8 +433,8 @@ body > canvas[data-id="canvas"] {
align-items: center;
gap: 2px;
padding: 0 8px;
- background: #263544;
- border-top: 1px solid #415263;
+ background: var(--cad-chrome);
+ border-top: 1px solid var(--cad-line);
}
.cad-statusbar button {
height: 21px;
@@ -412,20 +442,20 @@ body > canvas[data-id="canvas"] {
border: 1px solid transparent;
border-radius: 2px;
background: transparent;
- color: #9caebf;
+ color: var(--cad-text-dim);
font-size: 10px;
}
.cad-statusbar button:hover {
- background: #33485b;
+ background: var(--cad-hover);
}
.cad-statusbar button[data-active="true"] {
- border-color: #4f9bd0;
- background: #285579;
- color: white;
+ border-color: var(--cad-accent);
+ background: var(--color-primary);
+ color: var(--color-primary-text);
}
.cad-statusbar__hint {
margin-left: auto;
- color: #7f91a2;
+ color: var(--cad-text-muted);
font-size: 10px;
}
@@ -441,11 +471,11 @@ body > canvas[data-id="canvas"] {
/* 표(가장 넓은 자식) 기준 폭 고정 → 접었다 펴도 폭이 변하지 않는다 */
width: max-content;
max-width: min(1040px, calc(100vw - var(--cad-panel-width) - 24px));
- border: 1px solid #465565;
+ border: 1px solid var(--cad-line);
border-radius: 6px;
- background: #202b37f2;
- box-shadow: 0 4px 16px #0009;
- color: #dce6f2;
+ background: var(--cad-chrome-raised);
+ box-shadow: var(--shadow-lg);
+ color: var(--cad-text);
}
.cad-qtable__header {
@@ -463,12 +493,12 @@ body > canvas[data-id="canvas"] {
min-width: 0;
}
.cad-qtable__title strong {
- color: #f7fbff;
+ color: var(--color-text);
font-size: 14px;
}
.cad-qtable__title span {
overflow: hidden;
- color: #91a2b5;
+ color: var(--cad-text-dim);
font-size: 11px;
text-overflow: ellipsis;
white-space: nowrap;
@@ -480,26 +510,26 @@ body > canvas[data-id="canvas"] {
font-style: normal;
}
.cad-qtable__status[data-confirmed="true"] {
- background: #1f4d33;
- color: #7fdca4;
+ background: color-mix(in srgb, var(--color-success) 22%, transparent);
+ color: var(--color-success);
}
.cad-qtable__status[data-confirmed="false"] {
- background: #4a3a1c;
- color: #e6c07a;
+ background: color-mix(in srgb, var(--color-warning) 22%, transparent);
+ color: var(--color-warning);
}
.cad-qtable__nav,
.cad-qtable__collapse {
width: 26px;
height: 24px;
- border: 1px solid #40505f;
+ border: 1px solid var(--cad-line);
border-radius: 4px;
- background: #2a3745;
- color: #cdd8e4;
+ background: var(--cad-chrome-sunken);
+ color: var(--cad-text);
font-size: 13px;
}
.cad-qtable__nav:hover:not(:disabled),
.cad-qtable__collapse:hover {
- background: #31516b;
+ background: var(--cad-hover);
}
.cad-qtable__nav:disabled {
opacity: 0.35;
@@ -525,17 +555,17 @@ body > canvas[data-id="canvas"] {
.cad-qtable th,
.cad-qtable td {
padding: 0;
- border: 1px solid #40505f;
+ border: 1px solid var(--cad-line);
font-size: 11px;
text-align: center;
vertical-align: middle;
}
.cad-qtable__label {
padding: 2px 9px;
- color: #a9bccd;
+ color: var(--cad-text-dim);
font-weight: 600;
white-space: nowrap;
- background: #26333f;
+ background: var(--cad-chrome);
}
.cad-qtable__label--vertical {
width: 18px;
@@ -548,18 +578,18 @@ body > canvas[data-id="canvas"] {
padding: 3px 4px;
border: 0;
background: transparent;
- color: #eaf1f8;
+ color: var(--cad-text);
font: inherit;
text-align: center;
}
.cad-qtable__value input:focus {
- outline: 2px solid #4ca6e8;
+ outline: 2px solid var(--color-focus-ring);
outline-offset: -2px;
- background: #12324a;
+ background: var(--cad-hover);
}
.cad-qtable__value--derived input {
- color: #8fa3b5;
- background: #1a242e;
+ color: var(--cad-text-muted);
+ background: var(--cad-chrome-sunken);
cursor: not-allowed;
}
diff --git a/B07_DesignDetail/openwebcad/src/components/Button.tsx b/B07_DesignDetail/openwebcad/src/components/Button.tsx
index 0b139b7f..b1f3f087 100644
--- a/B07_DesignDetail/openwebcad/src/components/Button.tsx
+++ b/B07_DesignDetail/openwebcad/src/components/Button.tsx
@@ -37,11 +37,9 @@ export const Button: FC = ({
left = null,
right = null,
}) => {
+ // 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만.
const classParts = [
- 'font-semibold py-4 h-10 flex flex-row justify-start w-full items-center hover:bg-blue-500 hover:text-white hover:border-transparent',
- type === 'regular' ? 'bg-gray-950 text-blue-500' : '',
- type === 'transparent' ? 'bg-transparent text-blue-500' : '',
- active ? 'bg-blue-500 text-white border-transparent hover:bg-blue-400' : '',
+ 'font-semibold py-4 h-10 flex flex-row justify-start w-full items-center',
size === 'regular' ? 'pl-2 pr-2 gap-2' : '',
size === 'small' ? 'pl-2 pr-2 gap-0' : '',
className || '',
@@ -68,7 +66,7 @@ export const Button: FC = ({
(iconName && (
))}
{left}
diff --git a/B07_DesignDetail/openwebcad/src/components/LayerManager.tsx b/B07_DesignDetail/openwebcad/src/components/LayerManager.tsx
index 2891d5f6..c0962b7b 100644
--- a/B07_DesignDetail/openwebcad/src/components/LayerManager.tsx
+++ b/B07_DesignDetail/openwebcad/src/components/LayerManager.tsx
@@ -93,7 +93,7 @@ export const LayerManager: FC = ({
title="Set this layer as active"
active={activeLayerId === layer.id}
onClick={(evt) => handleLayerClick(evt, layer.id)}
- className="data-[active=true]:text-white flex-grow"
+ className="flex-grow"
left={
<>