cadcode 0.1.0 → 0.3.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/client/viewer.css CHANGED
@@ -1,14 +1,30 @@
1
+ /* The dev viewer as the playground's photo studio, without the code sheet: the model
2
+ stands on a soft, seamless backdrop that fills the window, and the controls float
3
+ on it in small groups, one per corner. Top left says what is on screen (the file,
4
+ the part), top right takes it away (PNG, STL), bottom left measures it, bottom
5
+ right changes how it is drawn. One accent, filament yellow, for the primary action
6
+ and the current selection; red only for faults. */
1
7
  :root {
2
- --bg: #eef0f3;
3
- --panel: rgba(255, 255, 255, 0.86);
4
- --text: #1d2228;
5
- --muted: #6b7480;
6
- --border: rgba(20, 30, 40, 0.12);
7
- --ok: #2f9e5b;
8
- --busy: #c98a12;
9
- --bad: #c8372d;
10
- font: 13px/1.4 ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
11
- color: var(--text);
8
+ --stage-light: #ffffff;
9
+ --stage-mid: #f2f3f5;
10
+ --stage-edge: #e2e5e9;
11
+ --ink: #1f2833;
12
+ --ink-soft: #77818d;
13
+ --glass: rgba(255, 255, 255, 0.72);
14
+ --hairline: rgba(31, 40, 51, 0.08);
15
+ --lift: 0 1px 2px rgba(31, 40, 51, 0.06), 0 8px 24px -8px rgba(31, 40, 51, 0.14);
16
+ --pop: 0 2px 6px rgba(31, 40, 51, 0.06), 0 18px 48px -12px rgba(31, 40, 51, 0.28);
17
+ --filament: #f2c14e;
18
+ --filament-deep: #dcaa2c;
19
+ --selected: #fdf6e3;
20
+ --fault: #c2412d;
21
+ --ok: #3f9a62;
22
+ --gap: 20px;
23
+ --ui: 'Instrument Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
24
+ --mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
25
+ font: 14px/1.45 var(--ui);
26
+ color: var(--ink);
27
+ accent-color: var(--ink);
12
28
  }
13
29
 
14
30
  * {
@@ -20,118 +36,241 @@ body {
20
36
  margin: 0;
21
37
  height: 100%;
22
38
  overflow: hidden;
23
- background: var(--bg);
39
+ background: var(--stage-mid);
24
40
  }
25
41
 
26
- #view {
42
+ [hidden] {
43
+ display: none !important;
44
+ }
45
+
46
+ :focus-visible {
47
+ outline: 2px solid var(--ink);
48
+ outline-offset: 2px;
49
+ }
50
+
51
+ button {
52
+ font: inherit;
53
+ color: inherit;
54
+ cursor: pointer;
55
+ }
56
+
57
+ button:disabled {
58
+ cursor: progress;
59
+ opacity: 0.5;
60
+ }
61
+
62
+ p {
63
+ margin: 0;
64
+ }
65
+
66
+ /* --- Stage ------------------------------------------------------------------ */
67
+
68
+ /* The canvas is transparent; the backdrop is a light sweep, brightest where the
69
+ model stands, a little above the middle. */
70
+ .stage {
27
71
  position: fixed;
28
72
  inset: 0;
73
+ background: radial-gradient(ellipse 70% 80% at 50% 42%, var(--stage-light) 0%, var(--stage-mid) 55%, var(--stage-edge) 100%);
74
+ }
75
+
76
+ #view {
77
+ position: absolute;
78
+ inset: 0;
29
79
  width: 100%;
30
80
  height: 100%;
31
81
  display: block;
82
+ transition: opacity 0.25s ease, filter 0.25s ease;
32
83
  }
33
84
 
34
- /* Toolbar on the left, program output on the right; they share one row so they never overlap. */
35
- .top {
36
- position: fixed;
37
- top: 12px;
38
- left: 12px;
39
- right: 12px;
85
+ .corner {
86
+ position: absolute;
87
+ z-index: 2;
40
88
  display: flex;
41
- justify-content: space-between;
89
+ align-items: center;
90
+ gap: 10px;
91
+ }
92
+
93
+ .top-left {
94
+ top: var(--gap);
95
+ left: var(--gap);
96
+ /* Leaves room for the export pills; a long path gives way first. */
97
+ right: calc(var(--gap) + 260px);
98
+ pointer-events: none;
99
+ }
100
+
101
+ .top-left > * {
102
+ pointer-events: auto;
103
+ }
104
+
105
+ .top-right {
106
+ top: var(--gap);
107
+ right: var(--gap);
108
+ }
109
+
110
+ .bottom-left {
111
+ left: var(--gap);
112
+ bottom: var(--gap);
113
+ right: calc(var(--gap) + 240px);
114
+ flex-direction: column;
42
115
  align-items: flex-start;
43
- gap: 12px;
116
+ gap: 14px;
44
117
  pointer-events: none;
45
118
  }
46
119
 
47
- .top > * {
120
+ .bottom-left > * {
48
121
  pointer-events: auto;
49
122
  }
50
123
 
51
- .bar {
52
- flex: 0 1 auto;
53
- min-width: 0;
54
- display: flex;
124
+ .bottom-right {
125
+ right: var(--gap);
126
+ bottom: var(--gap);
127
+ }
128
+
129
+ /* --- Floating controls -------------------------------------------------------- */
130
+
131
+ .brand {
132
+ flex-shrink: 0;
133
+ display: inline-flex;
55
134
  align-items: center;
56
- gap: 12px;
57
- padding: 6px 8px 6px 12px;
58
- background: var(--panel);
59
- backdrop-filter: blur(8px);
60
- border: 1px solid var(--border);
61
- border-radius: 10px;
62
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
135
+ gap: 9px;
136
+ margin-right: 8px;
137
+ font-weight: 600;
138
+ font-size: 17px;
139
+ letter-spacing: -0.01em;
63
140
  }
64
141
 
65
- .bar > :not(.file) {
142
+ .pill {
66
143
  flex-shrink: 0;
144
+ display: inline-flex;
145
+ align-items: center;
146
+ gap: 8px;
147
+ height: 36px;
148
+ padding: 0 14px;
149
+ background: var(--glass);
150
+ -webkit-backdrop-filter: blur(12px) saturate(1.4);
151
+ backdrop-filter: blur(12px) saturate(1.4);
152
+ border: 1px solid var(--hairline);
153
+ border-radius: 999px;
154
+ box-shadow: var(--lift);
155
+ font-weight: 500;
156
+ white-space: nowrap;
157
+ transition: background 0.15s ease;
67
158
  }
68
159
 
69
- .brand {
70
- font-weight: 650;
71
- letter-spacing: 0.02em;
160
+ button.pill:hover {
161
+ background: #fff;
72
162
  }
73
163
 
164
+ .pill.primary {
165
+ background: var(--filament);
166
+ border-color: var(--filament-deep);
167
+ }
168
+
169
+ .pill.primary:hover {
170
+ background: var(--filament-deep);
171
+ }
172
+
173
+ .pill[aria-pressed='true'] {
174
+ background: var(--ink);
175
+ border-color: var(--ink);
176
+ color: #fff;
177
+ }
178
+
179
+ .pill[aria-pressed='true']:hover {
180
+ background: var(--ink);
181
+ }
182
+
183
+ .pill.round {
184
+ justify-content: center;
185
+ width: 40px;
186
+ height: 40px;
187
+ padding: 0;
188
+ }
189
+
190
+ /* The program being watched: its folder quietly, its name plainly. A long path
191
+ gives up its folder first; the name always stays. */
74
192
  .file {
75
- display: flex;
193
+ flex-shrink: 1;
76
194
  min-width: 0;
77
- color: var(--muted);
78
- font-family: ui-monospace, 'SF Mono', Menlo, monospace;
79
- font-size: 12px;
80
- white-space: nowrap;
195
+ gap: 0;
196
+ font-family: var(--mono);
197
+ font-size: 12.5px;
198
+ font-weight: 400;
199
+ overflow: hidden;
81
200
  }
82
201
 
83
- /* A long path gives up its directory first; the file name always stays. */
84
202
  .file .dir {
85
203
  min-width: 0;
86
204
  overflow: hidden;
87
205
  text-overflow: ellipsis;
206
+ color: var(--ink-soft);
88
207
  }
89
208
 
90
209
  .file .name {
91
210
  flex-shrink: 0;
211
+ font-weight: 500;
212
+ }
213
+
214
+ .field {
215
+ color: var(--ink-soft);
216
+ }
217
+
218
+ .field strong {
219
+ font-weight: 600;
220
+ color: var(--ink);
92
221
  }
93
222
 
94
- .part {
223
+ .field svg {
224
+ margin-left: -2px;
225
+ }
226
+
227
+ .tools {
95
228
  display: flex;
96
- align-items: center;
97
- gap: 6px;
98
- color: var(--muted);
229
+ gap: 8px;
99
230
  }
100
231
 
101
- select,
102
- button {
103
- font: inherit;
104
- color: var(--text);
105
- background: #fff;
106
- border: 1px solid var(--border);
107
- border-radius: 6px;
108
- padding: 3px 8px;
232
+ /* --- The readout: the model's size, set large ----------------------------------- */
233
+
234
+ .readout .size {
235
+ font-size: clamp(28px, 3.4vw, 44px);
236
+ font-weight: 400;
237
+ line-height: 1.05;
238
+ letter-spacing: -0.03em;
239
+ font-variant-numeric: tabular-nums;
109
240
  }
110
241
 
111
- button {
112
- cursor: pointer;
242
+ .readout .size small {
243
+ margin-left: 0.2em;
244
+ font-size: 0.42em;
245
+ letter-spacing: 0;
246
+ color: var(--ink-soft);
247
+ }
248
+
249
+ .readout .meta {
250
+ display: flex;
251
+ flex-wrap: wrap;
252
+ column-gap: 16px;
253
+ margin-top: 8px;
254
+ color: var(--ink-soft);
255
+ font-variant-numeric: tabular-nums;
113
256
  }
114
257
 
115
- button:hover,
116
- select:hover {
117
- border-color: rgba(20, 30, 40, 0.28);
258
+ .readout .meta span:empty {
259
+ display: none;
118
260
  }
119
261
 
120
262
  .status {
121
263
  display: inline-flex;
122
264
  align-items: center;
123
- gap: 6px;
124
- color: var(--muted);
125
- white-space: nowrap;
126
- font-variant-numeric: tabular-nums;
265
+ gap: 7px;
127
266
  }
128
267
 
129
268
  .status::before {
130
269
  content: '';
131
- width: 8px;
132
- height: 8px;
270
+ width: 7px;
271
+ height: 7px;
133
272
  border-radius: 50%;
134
- background: var(--muted);
273
+ background: var(--ink-soft);
135
274
  }
136
275
 
137
276
  .status[data-state='ready']::before {
@@ -140,77 +279,252 @@ select:hover {
140
279
 
141
280
  .status[data-state='building']::before,
142
281
  .status[data-state='evaluating']::before {
143
- background: var(--busy);
282
+ background: var(--filament-deep);
283
+ }
284
+
285
+ .status[data-state='error'],
286
+ .status[data-state='disconnected'] {
287
+ color: var(--fault);
288
+ font-weight: 500;
144
289
  }
145
290
 
146
291
  .status[data-state='error']::before,
147
292
  .status[data-state='disconnected']::before {
148
- background: var(--bad);
293
+ background: var(--fault);
149
294
  }
150
295
 
151
- .error {
152
- position: fixed;
153
- left: 12px;
154
- right: 12px;
155
- bottom: 40px;
156
- max-height: 45vh;
157
- overflow: auto;
158
- background: #fff6f5;
159
- border: 1px solid rgba(200, 55, 45, 0.35);
160
- border-left: 4px solid var(--bad);
161
- border-radius: 8px;
162
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
296
+ .hint {
297
+ font-size: 12.5px;
298
+ color: var(--ink-soft);
299
+ pointer-events: none !important;
163
300
  }
164
301
 
165
- .error pre {
166
- margin: 0;
167
- padding: 12px 14px;
168
- font: 12px/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
169
- white-space: pre-wrap;
170
- color: #7a1d16;
302
+ /* --- Cards: logs and errors ----------------------------------------------------- */
303
+
304
+ .card {
305
+ background: rgba(255, 255, 255, 0.9);
306
+ -webkit-backdrop-filter: blur(12px);
307
+ backdrop-filter: blur(12px);
308
+ border: 1px solid var(--hairline);
309
+ border-radius: 14px;
310
+ box-shadow: var(--lift);
171
311
  }
172
312
 
313
+ /* The program's console output, hanging under the file it came from. */
173
314
  .logs {
174
- flex: 0 1 auto;
175
- min-width: 0;
176
- max-width: min(420px, 40vw);
177
- max-height: 40vh;
315
+ position: absolute;
316
+ z-index: 2;
317
+ top: calc(var(--gap) + 56px);
318
+ left: var(--gap);
319
+ max-width: min(480px, calc(100% - 2 * var(--gap)));
320
+ max-height: 35%;
178
321
  overflow: auto;
179
- padding: 8px 10px;
180
- background: var(--panel);
181
- border: 1px solid var(--border);
182
- border-radius: 10px;
183
- font: 12px/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
322
+ padding: 10px 14px;
323
+ font: 12px/1.55 var(--mono);
184
324
  white-space: pre-wrap;
185
325
  }
186
326
 
187
327
  .logs .log-warn {
188
- color: var(--busy);
328
+ color: #a8730b;
189
329
  }
190
330
 
191
331
  .logs .log-error {
192
- color: var(--bad);
332
+ color: var(--fault);
193
333
  }
194
334
 
195
- .hint {
196
- position: fixed;
197
- left: 12px;
198
- bottom: 12px;
199
- color: var(--muted);
200
- font-size: 12px;
335
+ .error {
336
+ max-width: min(720px, 100%);
337
+ max-height: 42vh;
338
+ overflow: auto;
339
+ border-left: 3px solid var(--fault);
340
+ }
341
+
342
+ .error pre {
343
+ margin: 0;
344
+ padding: 12px 16px;
345
+ font: 12.5px/1.55 var(--mono);
346
+ white-space: pre-wrap;
347
+ color: #8a2a1c;
348
+ }
349
+
350
+ /* --- Busy ------------------------------------------------------------------------ */
351
+ /* While the program rebuilds: a line runs along the top and the model on screen
352
+ steps back. With no model yet, a line of text in the middle instead. Both wait a
353
+ moment, so a quick rebuild does not flicker. A lost connection steps the model
354
+ back the same way, without the running line. */
355
+
356
+ .progress {
357
+ position: absolute;
358
+ z-index: 3;
359
+ top: 0;
360
+ left: 0;
361
+ right: 0;
362
+ height: 2px;
363
+ overflow: hidden;
364
+ opacity: 0;
365
+ transition: opacity 0.2s ease;
366
+ }
367
+
368
+ .progress::before {
369
+ content: '';
370
+ position: absolute;
371
+ inset: 0 auto 0 0;
372
+ width: 35%;
373
+ background: linear-gradient(90deg, transparent, var(--filament-deep), transparent);
374
+ animation: run 1.1s ease-in-out infinite;
375
+ }
376
+
377
+ @keyframes run {
378
+ from {
379
+ translate: -100% 0;
380
+ }
381
+ to {
382
+ translate: 300% 0;
383
+ }
384
+ }
385
+
386
+ .empty {
387
+ position: absolute;
388
+ top: 50%;
389
+ left: 50%;
390
+ translate: -50% -50%;
391
+ color: var(--ink-soft);
392
+ font-size: 15px;
393
+ opacity: 0;
201
394
  pointer-events: none;
395
+ transition: opacity 0.2s ease;
396
+ }
397
+
398
+ .stage[data-busy] .progress {
399
+ opacity: 1;
400
+ transition-delay: 0.15s;
401
+ }
402
+
403
+ .stage[data-busy] #view,
404
+ .stage[data-offline] #view {
405
+ opacity: 0.45;
406
+ filter: saturate(0.4);
407
+ transition-delay: 0.15s;
202
408
  }
203
409
 
410
+ .stage[data-empty]:is([data-busy], [data-offline]) .empty {
411
+ opacity: 1;
412
+ transition-delay: 0.15s;
413
+ }
414
+
415
+ /* --- Part menu ------------------------------------------------------------------ */
416
+ /* A list of the program's parts hanging under the Part pill. */
417
+
418
+ .menu {
419
+ position: fixed;
420
+ inset: auto;
421
+ min-width: 180px;
422
+ max-height: min(420px, calc(100dvh - 84px));
423
+ margin: 0;
424
+ padding: 6px;
425
+ display: none;
426
+ flex-direction: column;
427
+ gap: 2px;
428
+ overflow-y: auto;
429
+ background: #fff;
430
+ color: var(--ink);
431
+ border: 1px solid var(--hairline);
432
+ border-radius: 14px;
433
+ box-shadow: var(--pop);
434
+ transform-origin: 24px 0;
435
+ }
436
+
437
+ .menu:popover-open {
438
+ display: flex;
439
+ animation: drop 0.14s ease-out;
440
+ }
441
+
442
+ @keyframes drop {
443
+ from {
444
+ opacity: 0;
445
+ scale: 0.97;
446
+ translate: 0 -4px;
447
+ }
448
+ }
449
+
450
+ .menu button {
451
+ display: flex;
452
+ align-items: center;
453
+ justify-content: space-between;
454
+ gap: 16px;
455
+ padding: 7px 12px;
456
+ text-align: left;
457
+ font-weight: 500;
458
+ white-space: nowrap;
459
+ background: transparent;
460
+ border: 0;
461
+ border-radius: 9px;
462
+ }
463
+
464
+ .menu button:hover,
465
+ .menu button:focus-visible {
466
+ background: var(--stage-mid);
467
+ outline: none;
468
+ }
469
+
470
+ .menu button[aria-checked='true'] {
471
+ background: var(--selected);
472
+ font-weight: 600;
473
+ }
474
+
475
+ .menu button[aria-checked='true']::after {
476
+ content: '';
477
+ width: 7px;
478
+ height: 7px;
479
+ border-radius: 50%;
480
+ background: var(--filament-deep);
481
+ }
482
+
483
+ @media (prefers-reduced-motion: reduce) {
484
+ .progress::before {
485
+ animation: none;
486
+ width: 100%;
487
+ opacity: 0.6;
488
+ }
489
+ #view,
490
+ .progress,
491
+ .empty {
492
+ transition: none !important;
493
+ }
494
+ .menu:popover-open {
495
+ animation: none;
496
+ }
497
+ }
498
+
499
+ /* Phones: the brand keeps only its mark, the path goes, and the readout
500
+ rises above the display tools. */
204
501
  @media (max-width: 720px) {
502
+ .stage {
503
+ --gap: 12px;
504
+ }
505
+ .brand {
506
+ font-size: 0;
507
+ gap: 0;
508
+ margin-right: 0;
509
+ }
510
+ .top-left {
511
+ right: calc(var(--gap) + 170px);
512
+ gap: 8px;
513
+ }
514
+ .top-right {
515
+ gap: 6px;
516
+ }
205
517
  .file,
518
+ .wide,
206
519
  .hint {
207
520
  display: none;
208
521
  }
209
- .logs {
210
- position: fixed;
211
- left: 12px;
212
- right: 12px;
213
- bottom: 40px;
214
- max-width: none;
522
+ .bottom-left {
523
+ right: var(--gap);
524
+ bottom: calc(var(--gap) + 56px);
525
+ gap: 10px;
526
+ }
527
+ .readout .size {
528
+ font-size: 26px;
215
529
  }
216
530
  }