three-slicer 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +661 -0
- package/README.md +44 -0
- package/components/README.md +39 -0
- package/components/dist/SettingsPanel.js +180 -0
- package/components/styles.css +84 -0
- package/data/README.md +22 -0
- package/data/config-schema-builddump.json +12921 -0
- package/data/config-schema.json +12685 -0
- package/data/invalidation-map.json +631 -0
- package/data/toggle-rules.json +2277 -0
- package/data/ui-tree.json +1586 -0
- package/engine/README.md +52 -0
- package/engine/examples/headless.mjs +37 -0
- package/engine/index.d.ts +29 -0
- package/engine/index.js +45 -0
- package/engine/src/settings.js +130 -0
- package/engine/src/slicer.worker.js +73 -0
- package/engine/src/slicer_core.js +0 -0
- package/engine/src/slicer_core.mt.js +0 -0
- package/engine/src/toggle_eval.js +78 -0
- package/package.json +74 -0
- package/viewer/README.md +68 -0
- package/viewer/dist/Viewport.js +1108 -0
- package/viewer/dist/make_worker.js +9 -0
- package/viewer/dist/model_loaders.js +144 -0
- package/viewer/dist/toolpath_gpu.js +386 -0
- package/viewer/src/Viewport.jsx +1353 -0
- package/viewer/src/assets/add.svg +1 -0
- package/viewer/src/assets/arrange.svg +1 -0
- package/viewer/src/assets/boolean.svg +1 -0
- package/viewer/src/assets/cut.svg +1 -0
- package/viewer/src/assets/delete.svg +1 -0
- package/viewer/src/assets/deleteall.svg +1 -0
- package/viewer/src/assets/duplicate.svg +1 -0
- package/viewer/src/assets/measure.svg +1 -0
- package/viewer/src/assets/mmu.svg +1 -0
- package/viewer/src/assets/move.svg +1 -0
- package/viewer/src/assets/negative.svg +1 -0
- package/viewer/src/assets/onbed.svg +1 -0
- package/viewer/src/assets/open.svg +1 -0
- package/viewer/src/assets/orient.svg +1 -0
- package/viewer/src/assets/paint.svg +1 -0
- package/viewer/src/assets/rotate.svg +1 -0
- package/viewer/src/assets/scale.svg +1 -0
- package/viewer/src/assets/seam.svg +1 -0
- package/viewer/src/assets/split.svg +1 -0
- package/viewer/src/assets/textmark.svg +1 -0
- package/viewer/src/assets/varlayer.svg +1 -0
- package/viewer/src/icons.js +23 -0
- package/viewer/src/make_worker.js +9 -0
- package/viewer/src/model_loaders.js +178 -0
- package/viewer/src/shadow_host.jsx +30 -0
- package/viewer/src/toolpath_gpu.js +430 -0
- package/viewer/styles.css +231 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/* Shadow DOM 격리(shadow_host.jsx) — 이 파일은 ?inline 으로 번들에 문자열로 내장돼
|
|
2
|
+
shadow root 에 주입된다. 외부 리셋(*{box-sizing} 등)이 닿지 않으므로 여기서 명시한다. */
|
|
3
|
+
:host { display: contents; }
|
|
4
|
+
* { box-sizing: border-box; }
|
|
5
|
+
|
|
6
|
+
/* ===== 27단계: 데스크톱형 셸 (상단바 · 좌측 레일 · 중앙 뷰포트 · 우측 사이드바) ===== */
|
|
7
|
+
.app-shell { position: absolute; inset: 0; display: flex; flex-direction: column; background: #0e1216; color: #cdd6dd;
|
|
8
|
+
font: 14px/1.5 -apple-system, "Segoe UI", sans-serif; }
|
|
9
|
+
.app-body { flex: 1 1 auto; display: flex; min-height: 0; }
|
|
10
|
+
|
|
11
|
+
/* S1 상단바 */
|
|
12
|
+
.topbar { flex: 0 0 44px; height: 44px; display: flex; align-items: center; padding: 0 12px; gap: 10px;
|
|
13
|
+
background: #161b21; border-bottom: 1px solid #262d35; }
|
|
14
|
+
.topbar .tb-left, .topbar .tb-right { flex: 1 1 0; display: flex; align-items: center; gap: 8px; }
|
|
15
|
+
.topbar .tb-right { justify-content: flex-end; }
|
|
16
|
+
.tb-logo { font-size: 15px; color: #e6edf3; letter-spacing: .01em; }
|
|
17
|
+
.tb-logo b { color: #00ae42; } .tb-re { font-size: 10px; background: #00ae42; color: #fff; padding: 1px 5px; border-radius: 4px; margin-left: 2px; }
|
|
18
|
+
.tb-btn { display: inline-flex; align-items: center; gap: 5px; background: #20272f; border: 1px solid #2c353d;
|
|
19
|
+
color: #cdd6dd; border-radius: 6px; padding: 4px 10px; cursor: pointer; font-size: 12px; }
|
|
20
|
+
.tb-btn img { width: 15px; height: 15px; }
|
|
21
|
+
.tb-btn:hover { background: #28313a; }
|
|
22
|
+
.tb-tabs { display: flex; border: 1px solid #2c353d; border-radius: 8px; overflow: hidden; flex: 0 0 auto; }
|
|
23
|
+
.tb-tabs button { padding: 6px 24px; border: 0; background: #1a2027; color: #9fb3c0; font-weight: 600; font-size: 13px; cursor: pointer; }
|
|
24
|
+
.tb-tabs button.on { background: #00ae42; color: #fff; }
|
|
25
|
+
.tb-tabs button:disabled { color: #5a6570; cursor: not-allowed; }
|
|
26
|
+
.tb-icon { width: 28px; height: 28px; border: 1px solid #2c353d; background: #20272f; color: #7d8b96; border-radius: 6px; font-size: 15px; cursor: not-allowed; }
|
|
27
|
+
|
|
28
|
+
/* S3 좌측 기즈모 레일 */
|
|
29
|
+
.left-rail { flex: 0 0 48px; display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 9px 0;
|
|
30
|
+
background: #12171c; border-right: 1px solid #232a31; }
|
|
31
|
+
.left-rail button { width: 36px; height: 36px; border: 1px solid transparent; border-radius: 8px; background: transparent;
|
|
32
|
+
cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0; }
|
|
33
|
+
.left-rail button img { width: 21px; height: 21px; opacity: .72; }
|
|
34
|
+
.left-rail button:hover { background: #1e252c; }
|
|
35
|
+
.left-rail button.on { background: #00ae42; }
|
|
36
|
+
.left-rail button.on img { opacity: 1; filter: brightness(1.6); }
|
|
37
|
+
.rail-sep { width: 26px; height: 1px; background: #2a323a; margin: 3px 0; }
|
|
38
|
+
|
|
39
|
+
/* 중앙 뷰포트 칼럼 */
|
|
40
|
+
.viewport-col { position: relative; flex: 1 1 auto; min-width: 0; background: #161a1e; }
|
|
41
|
+
.vp-top-toolbar { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); display: flex; align-items: center;
|
|
42
|
+
gap: 3px; background: rgba(18,23,28,.85); padding: 4px; border-radius: 9px; border: 1px solid #2a323a; z-index: 3;
|
|
43
|
+
/* 33단계: 버튼이 늘어남. flex-wrap 은 절대배치 박스의 shrink-to-fit 폭을 붕괴시켜(실측 848→424px)
|
|
44
|
+
내용이 다 들어가는데도 2줄로 접힌다. 한 줄을 유지하고, 좁은 뷰포트에서만 가로 스크롤로 흘린다. */
|
|
45
|
+
max-width: calc(100% - 24px); overflow-x: auto; scrollbar-width: none; }
|
|
46
|
+
.vp-top-toolbar::-webkit-scrollbar { display: none; }
|
|
47
|
+
.vp-top-toolbar > * { flex: 0 0 auto; }
|
|
48
|
+
.vp-top-toolbar button { width: 32px; height: 32px; border: 0; background: transparent; border-radius: 6px; cursor: pointer;
|
|
49
|
+
display: flex; align-items: center; justify-content: center; }
|
|
50
|
+
.vp-top-toolbar button img { width: 18px; height: 18px; opacity: .82; }
|
|
51
|
+
.vp-top-toolbar button:hover:not(:disabled) { background: #2a333c; }
|
|
52
|
+
.vp-top-toolbar button:disabled { opacity: .32; cursor: not-allowed; }
|
|
53
|
+
.vtt-sep { width: 1px; align-self: stretch; background: #333c45; margin: 3px 3px; }
|
|
54
|
+
|
|
55
|
+
/* 29단계-2: 플레이트 탭바 + 슬라이스 드롭다운 */
|
|
56
|
+
.plate-bar { position: absolute; right: 14px; bottom: 30px; display: flex; align-items: center; gap: 4px; z-index: 3;
|
|
57
|
+
background: rgba(18,23,28,.85); border: 1px solid #2a323a; border-radius: 9px; padding: 4px; }
|
|
58
|
+
.plate-bar button { min-width: 26px; height: 26px; border: 1px solid #2c353d; background: #1a2027; color: #9fb3c0;
|
|
59
|
+
border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; }
|
|
60
|
+
.plate-tab.on { background: #00ae42; color: #fff; border-color: #00ae42; }
|
|
61
|
+
.plate-add { color: #7ad19a !important; } .plate-del { color: #e08a8a !important; }
|
|
62
|
+
.plate-bar button:disabled { opacity: .4; cursor: not-allowed; }
|
|
63
|
+
.slice-dd { position: relative; flex: 1.4; }
|
|
64
|
+
.slice-dd .slice-btn { width: 100%; }
|
|
65
|
+
.slice-menu { position: absolute; bottom: calc(100% + 6px); left: 0; right: 0; background: #fff; border: 1px solid #d5d8dd;
|
|
66
|
+
border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,.18); overflow: hidden; z-index: 10; }
|
|
67
|
+
.slice-menu button { display: block; width: 100%; text-align: left; padding: 9px 12px; border: 0; background: #fff;
|
|
68
|
+
color: #24282e; cursor: pointer; font-size: 13px; }
|
|
69
|
+
.slice-menu button:hover { background: #eef7f1; }
|
|
70
|
+
|
|
71
|
+
/* 페인팅 플로팅 패널 */
|
|
72
|
+
.brush-panel { position: absolute; top: 58px; left: 14px; width: 214px; background: rgba(22,27,33,.95);
|
|
73
|
+
border: 1px solid #2c353d; border-radius: 10px; padding: 10px; z-index: 4; box-shadow: 0 3px 14px rgba(0,0,0,.45); }
|
|
74
|
+
.bp-title { font-weight: 600; margin-bottom: 6px; color: #cdd6dd; }
|
|
75
|
+
.bp-modes { display: flex; gap: 4px; flex-wrap: wrap; }
|
|
76
|
+
.bp-modes button { padding: 3px 9px; border: 1px solid #2c353d; background: #1a2027; color: #cdd6dd; border-radius: 5px; cursor: pointer; font-size: 12px; }
|
|
77
|
+
.bp-modes button.enf.on { background: #2b6cff; color: #fff; border-color: #2b6cff; }
|
|
78
|
+
.bp-modes button.blk.on { background: #e23b3b; color: #fff; border-color: #e23b3b; }
|
|
79
|
+
.bp-radius { display: block; font-size: 12px; color: #aebcc6; margin-top: 8px; }
|
|
80
|
+
.bp-radius input { width: 100%; }
|
|
81
|
+
.bp-counts { margin-top: 6px; font-size: 11px; }
|
|
82
|
+
|
|
83
|
+
/* Preview 좌하단 stats 카드 */
|
|
84
|
+
.stats-card { position: absolute; left: 14px; bottom: 32px; background: rgba(22,27,33,.93); border: 1px solid #2c353d;
|
|
85
|
+
border-radius: 10px; padding: 10px 14px; font-size: 13px; line-height: 1.7; z-index: 2; color: #cdd6dd; }
|
|
86
|
+
.stats-card b { color: #9fe3bd; }
|
|
87
|
+
|
|
88
|
+
/* S4 우측 사이드바 (라이트) */
|
|
89
|
+
.sidebar { flex: 0 0 384px; width: 384px; display: flex; flex-direction: column; background: #eef0f2; color: #24282e;
|
|
90
|
+
border-left: 1px solid #d5d8dd; min-height: 0; }
|
|
91
|
+
.sidebar-scroll { flex: 1 1 auto; overflow-y: auto; padding: 10px; display: flex; flex-direction: column; gap: 10px; }
|
|
92
|
+
.side-card { background: #fff; border: 1px solid #e2e4e8; border-radius: 10px; padding: 10px 12px; }
|
|
93
|
+
.sc-head { font-weight: 700; font-size: 13px; color: #2a2f36; display: flex; align-items: center; gap: 6px; margin-bottom: 7px; }
|
|
94
|
+
.sc-count { background: #00ae42; color: #fff; font-size: 10px; border-radius: 8px; padding: 0 6px; font-weight: 700; }
|
|
95
|
+
.sc-head-btns { margin-left: auto; display: flex; gap: 4px; }
|
|
96
|
+
.sc-head-btns button { width: 22px; height: 22px; border: 1px solid #d3d6db; background: #f6f7f8; border-radius: 5px; cursor: pointer; font-size: 14px; line-height: 1; color: #333; }
|
|
97
|
+
.sc-head-btns button:disabled { opacity: .4; cursor: not-allowed; }
|
|
98
|
+
.sc-info { display: flex; justify-content: space-between; font-size: 12px; color: #5a6069; padding: 2px 0; }
|
|
99
|
+
.sc-info b { color: #24282e; }
|
|
100
|
+
.side-warn { margin: 0 2px; font-size: 12px; }
|
|
101
|
+
|
|
102
|
+
.filament-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; }
|
|
103
|
+
.filament-row input[type=color] { width: 28px; height: 22px; border: 1px solid #ccc; border-radius: 5px; padding: 0; cursor: pointer; background: none; }
|
|
104
|
+
.fil-t { font-weight: 700; font-size: 12px; width: 20px; }
|
|
105
|
+
.fil-swatch { width: 15px; height: 15px; border-radius: 4px; border: 1px solid rgba(0,0,0,.15); }
|
|
106
|
+
.fil-hex { font-size: 11px; margin-left: auto; }
|
|
107
|
+
|
|
108
|
+
.obj-list2 { list-style: none; margin: 0 0 6px; padding: 0; }
|
|
109
|
+
.obj-list2 li { display: flex; align-items: center; gap: 6px; padding: 3px 0; font-size: 12px; }
|
|
110
|
+
.obj-list2 li.obj-hidden { opacity: .5; }
|
|
111
|
+
.obj-eye { border: 0; background: none; cursor: pointer; font-size: 13px; padding: 0; }
|
|
112
|
+
.obj-list2 .obj-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #24282e; }
|
|
113
|
+
.obj-list2 .obj-ext { border: 1px solid #d0d3d8; border-radius: 5px; font-size: 11px; padding: 1px 3px; background: #fff; }
|
|
114
|
+
.obj-del { border: 0; background: #fbe9ec; color: #d0435a; border-radius: 5px; cursor: pointer; padding: 0 6px; }
|
|
115
|
+
.side-card .slice-support { color: #444; font-size: 12px; margin: 5px 0 0; }
|
|
116
|
+
|
|
117
|
+
/* 프로세스 카드 = 설정 패널 임베드 (라이트, 전폭) */
|
|
118
|
+
.process-card { padding: 8px; }
|
|
119
|
+
/* 임베드된 SettingsPanel 의 폭·테두리 오버라이드는 이제 패널 자신의 shadow 안 규칙
|
|
120
|
+
(:host(.sp-embedded), components/styles.css) — 여기서는 경계 너머라 닿지 않는다. */
|
|
121
|
+
|
|
122
|
+
/* Preview 컨트롤(라이트 카드 내부) 오버라이드 */
|
|
123
|
+
.side-card .view-type-row { color: #444; }
|
|
124
|
+
.side-card .view-type-row select, .side-card .obj-ext { background: #fff; color: #222; border: 1px solid #ccd0d5; }
|
|
125
|
+
.side-card .slice-layer label { color: #444; font-size: 12px; }
|
|
126
|
+
.side-card .grad-title { color: #333; } .side-card .grad-minmax { color: #666; }
|
|
127
|
+
.side-card .role-legend, .side-card .slice-travel { color: #555; }
|
|
128
|
+
.side-card .layer-ctl button { background: #f1f2f4; color: #333; border: 1px solid #d3d6db; }
|
|
129
|
+
.side-card .layer-ctl button.on { background: #00ae42; color: #fff; border-color: #00ae42; }
|
|
130
|
+
|
|
131
|
+
/* ⑤ 하단 고정 버튼 바 */
|
|
132
|
+
.side-bottom { flex: 0 0 auto; display: flex; gap: 8px; padding: 10px; border-top: 1px solid #dcdfe3; background: #e6e9ec; }
|
|
133
|
+
.side-bottom .slice-btn { flex: 1.4; margin: 0; }
|
|
134
|
+
.export-btn { flex: 1; text-align: center; padding: 9px; border-radius: 8px; background: #dfe6ea; color: #1f6feb;
|
|
135
|
+
border: 1px solid #c7d2da; text-decoration: none; font-weight: 600; cursor: pointer; font-size: 13px; }
|
|
136
|
+
.export-btn:hover { background: #d3dee4; }
|
|
137
|
+
.export-btn:disabled { color: #9aa4ad; background: #e9ecef; cursor: not-allowed; }
|
|
138
|
+
|
|
139
|
+
.vp-canvas { position: absolute; inset: 0; }
|
|
140
|
+
.vp-canvas canvas { display: block; }
|
|
141
|
+
.vp-canvas.fail { display: flex; align-items: center; justify-content: center; }
|
|
142
|
+
.vp-fallback { color: #f0c9a8; padding: 24px; text-align: center; max-width: 560px; line-height: 1.5; }
|
|
143
|
+
.vp-status { position: absolute; bottom: 8px; left: 12px; right: 12px; z-index: 1;
|
|
144
|
+
font-size: 11.5px; color: #9fe3bd; font-family: ui-monospace, monospace;
|
|
145
|
+
text-shadow: 0 1px 2px #000; pointer-events: none; }
|
|
146
|
+
/* ---- Slice 상태 표시 (뷰포트 오버레이) ---- */
|
|
147
|
+
.slice-warn { color: #f0c060; margin: 4px 0; }
|
|
148
|
+
.slice-btn { width: 100%; margin-top: 6px; padding: 7px; border: 0; border-radius: 7px;
|
|
149
|
+
background: #00ae42; color: #fff; font-weight: 700; cursor: pointer; }
|
|
150
|
+
.slice-btn:disabled { background: #3a4650; color: #8b98a2; cursor: not-allowed; }
|
|
151
|
+
.slice-err { color: #ff8b8b; margin-top: 6px; }
|
|
152
|
+
.slice-stats { margin-top: 8px; line-height: 1.5; }
|
|
153
|
+
.slice-stats b { color: #9fe3bd; }
|
|
154
|
+
.slice-layer { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
|
|
155
|
+
.slice-layer input[type=range] { width: 100%; }
|
|
156
|
+
.slice-travel { color: #aebcc6; display: flex; align-items: center; gap: 4px; }
|
|
157
|
+
|
|
158
|
+
/* 25단계 S6: 뷰 타입 셀렉트 · 이중 슬라이더 · 그라디언트 범례 */
|
|
159
|
+
.view-type-row { display: flex; align-items: center; justify-content: space-between; gap: 6px; color: #aebcc6; }
|
|
160
|
+
.view-type-row select { flex: 1; background: #10141a; color: #eee; border: 1px solid #2c353d; border-radius: 4px; padding: 2px 4px; }
|
|
161
|
+
.dual-slider { position: relative; height: 22px; }
|
|
162
|
+
.dual-slider input[type=range] { position: absolute; left: 0; top: 0; width: 100%; margin: 0; pointer-events: none; background: transparent; -webkit-appearance: none; appearance: none; }
|
|
163
|
+
.dual-slider input[type=range]::-webkit-slider-thumb { pointer-events: auto; -webkit-appearance: none; width: 13px; height: 13px; border-radius: 50%; background: #00ae42; border: 2px solid #0c1013; cursor: pointer; }
|
|
164
|
+
.dual-slider input[type=range]::-moz-range-thumb { pointer-events: auto; width: 13px; height: 13px; border-radius: 50%; background: #00ae42; border: 2px solid #0c1013; cursor: pointer; }
|
|
165
|
+
.dual-slider input[type=range]::-webkit-slider-runnable-track { height: 4px; background: #2c353d; border-radius: 2px; }
|
|
166
|
+
.dual-slider input[type=range]:first-child { z-index: 2; }
|
|
167
|
+
.layer-ctl { display: flex; align-items: center; gap: 8px; }
|
|
168
|
+
.layer-ctl button { padding: 2px 8px; border: 1px solid #2c353d; border-radius: 5px; background: #1a2027; color: #cdd6dd; font-size: 11px; cursor: pointer; }
|
|
169
|
+
.layer-ctl button.on { background: #00ae42; color: #fff; border-color: #00ae42; }
|
|
170
|
+
.grad-legend { margin-top: 6px; }
|
|
171
|
+
.grad-title { font-size: 11px; color: #cdd6dd; margin-bottom: 3px; }
|
|
172
|
+
.grad-bar { height: 10px; border-radius: 3px; border: 1px solid #2c353d; }
|
|
173
|
+
.grad-minmax { display: flex; justify-content: space-between; font-size: 10px; color: #aebcc6; margin-top: 2px; }
|
|
174
|
+
.role-legend { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 6px; font-size: 11px; color: #aebcc6; }
|
|
175
|
+
.role-item { display: inline-flex; align-items: center; gap: 4px; }
|
|
176
|
+
.role-item i { width: 10px; height: 6px; border-radius: 2px; display: inline-block; }
|
|
177
|
+
.role-item b { color: #cdd6dd; }
|
|
178
|
+
|
|
179
|
+
/* 26단계: 빈 씬 안내 + 드래그앤드롭 오버레이 */
|
|
180
|
+
.vp-canvas.drag-over { outline: 3px dashed #00ae42; outline-offset: -6px; }
|
|
181
|
+
.empty-hint { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
182
|
+
gap: 8px; pointer-events: none; color: #9fb3c0; text-align: center; }
|
|
183
|
+
.empty-hint .eh-icon { font-size: 46px; opacity: .8; }
|
|
184
|
+
.empty-hint .eh-title { font-size: 16px; font-weight: 600; color: #cdd6dd; }
|
|
185
|
+
.empty-hint .eh-sub { font-size: 12px; color: #7d8b96; letter-spacing: .04em; }
|
|
186
|
+
.empty-hint .eh-btn { pointer-events: auto; margin-top: 8px; padding: 7px 18px; border: 0; border-radius: 7px;
|
|
187
|
+
background: #00ae42; color: #fff; font-weight: 600; cursor: pointer; }
|
|
188
|
+
.empty-hint .eh-btn:hover { background: #06c04d; }
|
|
189
|
+
.drop-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
190
|
+
background: rgba(0,174,66,.14); color: #d7f7e3; font-size: 18px; font-weight: 700; pointer-events: none;
|
|
191
|
+
border: 3px dashed #00ae42; box-sizing: border-box; }
|
|
192
|
+
|
|
193
|
+
/* 오브젝트 분리 버튼 */
|
|
194
|
+
.obj-split { border: 0; background: transparent; cursor: pointer; padding: 0 3px; line-height: 0; }
|
|
195
|
+
.obj-split img { width: 13px; height: 13px; opacity: .6; }
|
|
196
|
+
.obj-split:hover img { opacity: 1; }
|
|
197
|
+
|
|
198
|
+
/* 우클릭 컨텍스트 메뉴 */
|
|
199
|
+
.ctx-scrim { position: absolute; inset: 0; z-index: 24; }
|
|
200
|
+
.ctx-menu { position: absolute; z-index: 25; min-width: 168px; padding: 4px;
|
|
201
|
+
background: #1b2027; border: 1px solid #2f3944; border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.5); }
|
|
202
|
+
.ctx-menu button { display: flex; width: 100%; align-items: center; justify-content: space-between; gap: 16px;
|
|
203
|
+
padding: 6px 10px; border: 0; border-radius: 5px; background: transparent; color: #dfe6ee;
|
|
204
|
+
font-size: 12px; text-align: left; cursor: pointer; }
|
|
205
|
+
.ctx-menu button:hover:not(:disabled) { background: #273140; }
|
|
206
|
+
.ctx-menu button:disabled { color: #64707d; cursor: not-allowed; }
|
|
207
|
+
.ctx-menu button.danger:hover:not(:disabled) { background: #4a2229; color: #ffb3b3; }
|
|
208
|
+
.ctx-menu hr { margin: 4px 6px; border: 0; border-top: 1px solid #2f3944; }
|
|
209
|
+
.ctx-key { color: #7d8b96; font-family: ui-monospace, monospace; font-size: 11px; }
|
|
210
|
+
|
|
211
|
+
/* 단축키 도움말 오버레이 ('?') */
|
|
212
|
+
.help-overlay { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
213
|
+
background: rgba(8,12,16,.72); z-index: 20; }
|
|
214
|
+
.help-card { background: #1b2027; color: #dfe6ee; border: 1px solid #2f3944; border-radius: 10px;
|
|
215
|
+
padding: 18px 22px; max-width: 720px; box-shadow: 0 12px 40px rgba(0,0,0,.5); }
|
|
216
|
+
.help-card h3 { margin: 0 0 12px; font-size: 15px; }
|
|
217
|
+
.help-card h4 { margin: 0 0 6px; font-size: 12px; color: #00ae42; text-transform: uppercase; letter-spacing: .04em; }
|
|
218
|
+
.help-cols { display: flex; gap: 26px; flex-wrap: wrap; }
|
|
219
|
+
.help-card dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 0; font-size: 12px; }
|
|
220
|
+
.help-card dt { color: #9fb0c2; font-family: ui-monospace, monospace; white-space: nowrap; }
|
|
221
|
+
.help-card dd { margin: 0; }
|
|
222
|
+
|
|
223
|
+
/* 멀티 오브젝트 목록 / 서포트 토글 (3단계) */
|
|
224
|
+
.slice-support { display: flex; align-items: center; gap: 5px; margin: 6px 0; color: #cdd6dd; font-size: 12px; }
|
|
225
|
+
|
|
226
|
+
.slice-support .muted { font-size: 10px; }
|
|
227
|
+
|
|
228
|
+
/* 프린터 카드 — 베드 폭×깊이 인라인 편집 */
|
|
229
|
+
.sc-bed { display: inline-flex; align-items: center; gap: 4px; font-weight: 700; color: #222; }
|
|
230
|
+
.sc-bed input { width: 54px; padding: 1px 4px; font-size: 12px; font-weight: 600; text-align: right;
|
|
231
|
+
border: 1px solid #ccd0d5; border-radius: 5px; background: #fff; color: #222; }
|