cadcode 0.2.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,123 +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;
162
+ }
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;
72
177
  }
73
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);
221
+ }
222
+
223
+ .field svg {
224
+ margin-left: -2px;
92
225
  }
93
226
 
94
- .part {
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);
113
247
  }
114
248
 
115
- button:disabled {
116
- cursor: progress;
117
- opacity: 0.55;
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;
118
256
  }
119
257
 
120
- button:hover,
121
- select:hover {
122
- border-color: rgba(20, 30, 40, 0.28);
258
+ .readout .meta span:empty {
259
+ display: none;
123
260
  }
124
261
 
125
262
  .status {
126
263
  display: inline-flex;
127
264
  align-items: center;
128
- gap: 6px;
129
- color: var(--muted);
130
- white-space: nowrap;
131
- font-variant-numeric: tabular-nums;
265
+ gap: 7px;
132
266
  }
133
267
 
134
268
  .status::before {
135
269
  content: '';
136
- width: 8px;
137
- height: 8px;
270
+ width: 7px;
271
+ height: 7px;
138
272
  border-radius: 50%;
139
- background: var(--muted);
273
+ background: var(--ink-soft);
140
274
  }
141
275
 
142
276
  .status[data-state='ready']::before {
@@ -145,77 +279,252 @@ select:hover {
145
279
 
146
280
  .status[data-state='building']::before,
147
281
  .status[data-state='evaluating']::before {
148
- 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;
149
289
  }
150
290
 
151
291
  .status[data-state='error']::before,
152
292
  .status[data-state='disconnected']::before {
153
- background: var(--bad);
293
+ background: var(--fault);
154
294
  }
155
295
 
156
- .error {
157
- position: fixed;
158
- left: 12px;
159
- right: 12px;
160
- bottom: 40px;
161
- max-height: 45vh;
162
- overflow: auto;
163
- background: #fff6f5;
164
- border: 1px solid rgba(200, 55, 45, 0.35);
165
- border-left: 4px solid var(--bad);
166
- border-radius: 8px;
167
- 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;
168
300
  }
169
301
 
170
- .error pre {
171
- margin: 0;
172
- padding: 12px 14px;
173
- font: 12px/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
174
- white-space: pre-wrap;
175
- 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);
176
311
  }
177
312
 
313
+ /* The program's console output, hanging under the file it came from. */
178
314
  .logs {
179
- flex: 0 1 auto;
180
- min-width: 0;
181
- max-width: min(420px, 40vw);
182
- 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%;
183
321
  overflow: auto;
184
- padding: 8px 10px;
185
- background: var(--panel);
186
- border: 1px solid var(--border);
187
- border-radius: 10px;
188
- font: 12px/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
322
+ padding: 10px 14px;
323
+ font: 12px/1.55 var(--mono);
189
324
  white-space: pre-wrap;
190
325
  }
191
326
 
192
327
  .logs .log-warn {
193
- color: var(--busy);
328
+ color: #a8730b;
194
329
  }
195
330
 
196
331
  .logs .log-error {
197
- color: var(--bad);
332
+ color: var(--fault);
198
333
  }
199
334
 
200
- .hint {
201
- position: fixed;
202
- left: 12px;
203
- bottom: 12px;
204
- color: var(--muted);
205
- 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;
206
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;
408
+ }
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
+ }
207
448
  }
208
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. */
209
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
+ }
210
517
  .file,
518
+ .wide,
211
519
  .hint {
212
520
  display: none;
213
521
  }
214
- .logs {
215
- position: fixed;
216
- left: 12px;
217
- right: 12px;
218
- bottom: 40px;
219
- 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;
220
529
  }
221
530
  }