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.
Files changed (54) hide show
  1. package/LICENSE.txt +661 -0
  2. package/README.md +44 -0
  3. package/components/README.md +39 -0
  4. package/components/dist/SettingsPanel.js +180 -0
  5. package/components/styles.css +84 -0
  6. package/data/README.md +22 -0
  7. package/data/config-schema-builddump.json +12921 -0
  8. package/data/config-schema.json +12685 -0
  9. package/data/invalidation-map.json +631 -0
  10. package/data/toggle-rules.json +2277 -0
  11. package/data/ui-tree.json +1586 -0
  12. package/engine/README.md +52 -0
  13. package/engine/examples/headless.mjs +37 -0
  14. package/engine/index.d.ts +29 -0
  15. package/engine/index.js +45 -0
  16. package/engine/src/settings.js +130 -0
  17. package/engine/src/slicer.worker.js +73 -0
  18. package/engine/src/slicer_core.js +0 -0
  19. package/engine/src/slicer_core.mt.js +0 -0
  20. package/engine/src/toggle_eval.js +78 -0
  21. package/package.json +74 -0
  22. package/viewer/README.md +68 -0
  23. package/viewer/dist/Viewport.js +1108 -0
  24. package/viewer/dist/make_worker.js +9 -0
  25. package/viewer/dist/model_loaders.js +144 -0
  26. package/viewer/dist/toolpath_gpu.js +386 -0
  27. package/viewer/src/Viewport.jsx +1353 -0
  28. package/viewer/src/assets/add.svg +1 -0
  29. package/viewer/src/assets/arrange.svg +1 -0
  30. package/viewer/src/assets/boolean.svg +1 -0
  31. package/viewer/src/assets/cut.svg +1 -0
  32. package/viewer/src/assets/delete.svg +1 -0
  33. package/viewer/src/assets/deleteall.svg +1 -0
  34. package/viewer/src/assets/duplicate.svg +1 -0
  35. package/viewer/src/assets/measure.svg +1 -0
  36. package/viewer/src/assets/mmu.svg +1 -0
  37. package/viewer/src/assets/move.svg +1 -0
  38. package/viewer/src/assets/negative.svg +1 -0
  39. package/viewer/src/assets/onbed.svg +1 -0
  40. package/viewer/src/assets/open.svg +1 -0
  41. package/viewer/src/assets/orient.svg +1 -0
  42. package/viewer/src/assets/paint.svg +1 -0
  43. package/viewer/src/assets/rotate.svg +1 -0
  44. package/viewer/src/assets/scale.svg +1 -0
  45. package/viewer/src/assets/seam.svg +1 -0
  46. package/viewer/src/assets/split.svg +1 -0
  47. package/viewer/src/assets/textmark.svg +1 -0
  48. package/viewer/src/assets/varlayer.svg +1 -0
  49. package/viewer/src/icons.js +23 -0
  50. package/viewer/src/make_worker.js +9 -0
  51. package/viewer/src/model_loaders.js +178 -0
  52. package/viewer/src/shadow_host.jsx +30 -0
  53. package/viewer/src/toolpath_gpu.js +430 -0
  54. 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; }