@womp/mcp-ui 0.1.19 → 0.1.20

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.
@@ -5,42 +5,131 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6
6
  <title>Camera</title>
7
7
  <style>
8
+ /*
9
+ * Every colour, size and radius comes from the host first (ChatGPT sends
10
+ * its own over the bridge) and falls back to these. Names stay local so
11
+ * the rules below read as design, not plumbing.
12
+ */
8
13
  :root {
9
14
  color-scheme: light dark;
15
+ --fg: var(--color-text-primary, #18181b);
16
+ --fg-2: var(--color-text-secondary, #6b6b70);
17
+ --bg: var(--color-background-primary, #ffffff);
18
+ --bg-2: var(--color-background-secondary, #f6f6f7);
19
+ --line: var(--color-border-primary, rgba(0, 0, 0, 0.08));
20
+ --line-2: var(--color-border-secondary, rgba(0, 0, 0, 0.14));
21
+ --radius: var(--border-radius-xl, 20px);
22
+ --radius-sm: var(--border-radius-md, 12px);
23
+ --pill: var(--border-radius-full, 999px);
24
+ --text-xs: var(--font-text-xs-size, 12px);
25
+ --text-sm: var(--font-text-sm-size, 14px);
26
+ --text-md: var(--font-text-md-size, 16px);
27
+ --stage-a: #f4f4f5;
28
+ --stage-b: #e4e4e7;
10
29
  }
30
+ @media (prefers-color-scheme: dark) {
31
+ :root:not([data-theme="light"]) {
32
+ --fg: var(--color-text-primary, #f4f4f5);
33
+ --fg-2: var(--color-text-secondary, #a1a1aa);
34
+ --bg: var(--color-background-primary, #1c1c1f);
35
+ --bg-2: var(--color-background-secondary, #26262a);
36
+ --line: var(--color-border-primary, rgba(255, 255, 255, 0.08));
37
+ --line-2: var(--color-border-secondary, rgba(255, 255, 255, 0.16));
38
+ --stage-a: #2a2a2e;
39
+ --stage-b: #1a1a1d;
40
+ }
41
+ }
42
+ :root[data-theme="dark"] {
43
+ --fg: var(--color-text-primary, #f4f4f5);
44
+ --fg-2: var(--color-text-secondary, #a1a1aa);
45
+ --bg: var(--color-background-primary, #1c1c1f);
46
+ --bg-2: var(--color-background-secondary, #26262a);
47
+ --line: var(--color-border-primary, rgba(255, 255, 255, 0.08));
48
+ --line-2: var(--color-border-secondary, rgba(255, 255, 255, 0.16));
49
+ --stage-a: #2a2a2e;
50
+ --stage-b: #1a1a1d;
51
+ }
52
+
11
53
  body {
12
54
  margin: 0;
13
- font-family: var(--font-sans, system-ui, -apple-system, "Segoe UI", sans-serif);
14
- color: var(--color-text-primary, #1b1b1a);
55
+ font-family: var(--font-sans, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif);
56
+ color: var(--fg);
57
+ -webkit-font-smoothing: antialiased;
58
+ }
59
+ [hidden] { display: none !important; }
60
+ button { font: inherit; color: inherit; }
61
+ button:focus-visible, input:focus-visible, summary:focus-visible {
62
+ outline: 2px solid var(--fg);
63
+ outline-offset: 2px;
15
64
  }
16
65
 
66
+ /*
67
+ * One card, two layouts. Inline: render, colours, price, Customize and
68
+ * Order. Fullscreen: the same card with every control, in a panel beside
69
+ * (wide) or under (narrow) the render. `.full-only` is the editing the
70
+ * inline card leaves to fullscreen.
71
+ */
72
+ :root:not([data-display-mode="fullscreen"]) .full-only { display: none !important; }
73
+ :root[data-display-mode="fullscreen"] .inline-only { display: none !important; }
74
+
17
75
  .card {
18
- display: flex;
19
- flex-direction: column;
20
- max-width: 680px;
21
- border-radius: var(--border-radius-lg, 14px);
76
+ display: grid;
77
+ grid-template-columns: minmax(0, 1fr);
78
+ max-width: 760px;
79
+ border-radius: var(--radius);
22
80
  overflow: hidden;
23
- background: var(--color-background-secondary, #f6f6f4);
24
- border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.1));
81
+ background: var(--bg);
82
+ border: 1px solid var(--line);
25
83
  box-sizing: border-box;
26
84
  }
27
- /* Fullscreen hands us the viewport: the viewer takes whatever the controls leave. */
85
+ /* The chat column is wide enough on desktop: render left, choices right. */
86
+ @media (min-width: 560px) {
87
+ :root:not([data-display-mode="fullscreen"]) .card { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
88
+ :root:not([data-display-mode="fullscreen"]) .stage { height: auto; min-height: 324px; margin: 8px 0 8px 8px; }
89
+ }
90
+
91
+ /* Narrow fullscreen: render on top, controls scroll under it, Order pinned. */
28
92
  :root[data-display-mode="fullscreen"] .card {
29
93
  max-width: none;
30
94
  height: 100vh;
31
95
  border-radius: 0;
32
96
  border: 0;
97
+ grid-template-rows: auto 40vh minmax(0, 1fr) auto;
98
+ }
99
+ :root[data-display-mode="fullscreen"] .stage { height: auto; margin: 12px 12px 0; border-radius: 16px; }
100
+ @media (min-width: 760px) {
101
+ :root[data-display-mode="fullscreen"] .stage { margin: 12px; }
102
+ }
103
+ /* Wide fullscreen: big render left, a fixed panel right. */
104
+ @media (min-width: 760px) {
105
+ :root[data-display-mode="fullscreen"] .card {
106
+ grid-template-columns: minmax(0, 1fr) 380px;
107
+ grid-template-rows: auto minmax(0, 1fr) auto;
108
+ }
109
+ :root[data-display-mode="fullscreen"] .top { grid-column: 1 / -1; }
110
+ :root[data-display-mode="fullscreen"] .panel { border-left: 1px solid var(--line); }
33
111
  }
34
112
 
113
+ .top {
114
+ display: flex;
115
+ align-items: center;
116
+ gap: 4px;
117
+ height: 56px;
118
+ padding: 0 16px 0 6px;
119
+ border-bottom: 1px solid var(--line);
120
+ }
121
+ .top strong { flex: 1; min-width: 0; font-size: var(--text-md); font-weight: 600; }
122
+ .top .muted { font-size: var(--text-sm); }
123
+
124
+ /* The render sits on a soft studio backdrop, never a flat grey box. */
125
+ /* The render is inset in the card with its own rounded frame. */
35
126
  .stage {
36
127
  position: relative;
37
- height: 360px;
38
- background: #d9d9d9;
39
- }
40
- :root[data-display-mode="fullscreen"] .stage {
41
- flex: 1;
42
- height: auto;
43
- min-height: 0;
128
+ height: 280px;
129
+ margin: 8px 8px 0;
130
+ border-radius: calc(var(--radius) - 8px);
131
+ overflow: hidden;
132
+ background: radial-gradient(120% 90% at 50% 35%, var(--stage-a), var(--stage-b));
44
133
  }
45
134
  .stage iframe {
46
135
  display: block;
@@ -48,82 +137,123 @@
48
137
  height: 100%;
49
138
  border: 0;
50
139
  }
51
- .stage iframe[hidden] { display: none; }
52
-
53
- .fallback {
140
+ .preview {
54
141
  position: absolute;
55
142
  inset: 0;
56
- overflow: auto;
57
- padding: 16px;
58
- box-sizing: border-box;
59
- background: var(--color-background-tertiary, #ecebe6);
60
- color: var(--color-text-primary, #1b1b1a);
61
- font-size: var(--font-text-sm-size, 13px);
62
- }
63
- .fallback[hidden] { display: none; }
64
- .fallback p { margin: 0 0 10px; }
65
- .fallback dl {
66
143
  display: grid;
67
- grid-template-columns: minmax(0, 1fr) auto;
68
- gap: 2px 12px;
144
+ place-items: center;
145
+ padding: 24px;
146
+ }
147
+ .preview svg { width: min(62%, 280px); height: auto; filter: drop-shadow(0 18px 22px rgba(0, 0, 0, 0.16)); }
148
+ .preview p {
149
+ position: absolute;
150
+ left: 0;
151
+ right: 0;
152
+ bottom: 12px;
69
153
  margin: 0;
70
- font-size: var(--font-text-xs-size, 12px);
154
+ text-align: center;
155
+ font-size: var(--text-xs);
156
+ color: var(--fg-2);
157
+ }
158
+
159
+ .panel {
160
+ display: flex;
161
+ flex-direction: column;
162
+ min-height: 0;
163
+ min-width: 0;
164
+ }
165
+ .scroll {
166
+ flex: 1;
167
+ min-height: 0;
168
+ overflow: auto;
169
+ display: grid;
170
+ align-content: start;
171
+ gap: 18px;
172
+ padding: 18px 18px 6px;
71
173
  }
72
- .fallback dt { color: var(--color-text-secondary, rgba(27, 27, 26, 0.62)); }
73
- .fallback dd { margin: 0; font-variant-numeric: tabular-nums; }
174
+ :root[data-display-mode="fullscreen"] .scroll { gap: 0; padding: 0; }
175
+
176
+ .title { display: flex; align-items: baseline; gap: 12px; }
177
+ .title > div { flex: 1; min-width: 0; display: grid; gap: 3px; }
178
+ .title h2 { margin: 0; font-size: 18px; font-weight: 650; letter-spacing: -0.01em; }
179
+ .title .price-tag { font-size: var(--text-md); font-weight: 600; font-variant-numeric: tabular-nums; }
180
+ .muted { margin: 0; font-size: var(--text-sm); color: var(--fg-2); }
74
181
 
75
- .controls {
182
+ /* Which part the swatches paint. */
183
+ .parts {
76
184
  display: grid;
185
+ grid-template-columns: 1fr 1fr;
186
+ gap: 4px;
187
+ padding: 4px;
188
+ border-radius: var(--pill);
189
+ background: var(--bg-2);
190
+ }
191
+ .parts button {
192
+ white-space: nowrap;
193
+ display: flex;
194
+ align-items: center;
195
+ justify-content: center;
77
196
  gap: 8px;
78
- padding: 12px 14px 0;
197
+ min-height: 36px;
198
+ padding: 0 12px;
199
+ border: 0;
200
+ border-radius: var(--pill);
201
+ background: transparent;
202
+ color: var(--fg-2);
203
+ font-size: var(--text-sm);
204
+ font-weight: 500;
205
+ cursor: pointer;
206
+ }
207
+ .parts button[aria-selected="true"] {
208
+ background: var(--bg);
209
+ color: var(--fg);
210
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
211
+ }
212
+ .dot {
213
+ width: 14px;
214
+ height: 14px;
215
+ border-radius: 50%;
216
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.14);
79
217
  }
80
- .row {
218
+
219
+ .group { display: grid; gap: 12px; min-width: 0; }
220
+ .group-head {
81
221
  display: flex;
82
222
  align-items: center;
83
223
  gap: 10px;
84
- min-width: 0;
85
- }
86
- .row > span {
87
- flex: none;
88
- width: 48px;
89
- font-size: var(--font-text-xs-size, 12px);
90
- color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
224
+ min-height: 28px;
225
+ font-size: var(--text-sm);
226
+ color: var(--fg-2);
91
227
  }
228
+ .group-head > span { flex: 1; min-width: 0; }
229
+ .group-head b { color: var(--fg); font-weight: 600; }
230
+
92
231
  .swatches {
93
- display: flex;
94
- flex-wrap: wrap;
95
- gap: 6px;
96
- min-width: 0;
232
+ display: grid;
233
+ grid-template-columns: repeat(auto-fill, minmax(30px, 1fr));
234
+ gap: 10px;
235
+ justify-items: center;
97
236
  }
98
- .swatch {
99
- width: 22px;
100
- height: 22px;
237
+ .swatch, .pick, .icon-button.tool {
238
+ position: relative;
239
+ width: 30px;
240
+ height: 30px;
101
241
  padding: 0;
102
242
  border-radius: 50%;
103
- border: 1px solid rgba(0, 0, 0, 0.18);
243
+ border: 0;
244
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
245
+ box-sizing: border-box;
104
246
  cursor: pointer;
247
+ transition: transform 120ms ease;
105
248
  }
249
+ .swatch:hover, .pick:hover { transform: scale(1.08); }
106
250
  /* The clear resin gets a hint of glass rather than a flat swatch. */
107
251
  .swatch.clear {
108
- background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0) 55%) !important;
109
- }
110
- .swatch[aria-pressed="true"] {
111
- outline: 2px solid var(--color-text-primary, #1b1b1a);
112
- outline-offset: 2px;
252
+ background-image: linear-gradient(135deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0) 60%) !important;
113
253
  }
114
-
254
+ .swatch[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--fg); }
115
255
  /* Any colour: the native picker (Chromium's has an eyedropper of its own). */
116
- .pick {
117
- position: relative;
118
- flex: none;
119
- width: 22px;
120
- height: 22px;
121
- border-radius: 50%;
122
- border: 1px solid rgba(0, 0, 0, 0.18);
123
- background: conic-gradient(#ff5e5e, #ffd93d, #6bdc6b, #4fc3f7, #7b6cf6, #f06bd8, #ff5e5e);
124
- cursor: pointer;
125
- box-sizing: border-box;
126
- }
256
+ .pick { background: conic-gradient(#ff5e5e, #ffd93d, #6bdc6b, #4fc3f7, #7b6cf6, #f06bd8, #ff5e5e); }
127
257
  .pick input {
128
258
  position: absolute;
129
259
  inset: 0;
@@ -134,168 +264,288 @@
134
264
  border: 0;
135
265
  cursor: pointer;
136
266
  }
267
+
137
268
  .icon-button {
138
269
  flex: none;
139
270
  display: inline-grid;
140
271
  place-items: center;
141
- width: 24px;
142
- height: 24px;
272
+ width: 32px;
273
+ height: 32px;
143
274
  padding: 0;
144
275
  border-radius: 50%;
145
- border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
276
+ border: 1px solid var(--line-2);
146
277
  background: transparent;
147
- color: var(--color-text-primary, #1b1b1a);
148
- font: inherit;
149
- font-size: 14px;
278
+ font-size: 16px;
150
279
  line-height: 1;
151
280
  cursor: pointer;
152
281
  }
153
- .icon-button[hidden] { display: none; }
154
- .icon-button:disabled { opacity: 0.4; cursor: default; }
282
+ .icon-button.tool { border: 0; background: var(--bg-2); }
283
+ .icon-button.close { width: 44px; height: 44px; border: 0; }
284
+ .icon-button:hover:not(:disabled) { background: var(--bg-2); }
285
+ .icon-button:disabled { opacity: 0.35; cursor: default; }
155
286
 
156
- .mode {
157
- flex: none;
287
+ .segmented {
158
288
  display: inline-flex;
159
- gap: 2px;
160
- padding: 2px;
161
- border-radius: var(--border-radius-full, 999px);
162
- border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
163
- }
164
- .mode button {
165
- font: inherit;
166
- font-size: var(--font-text-xs-size, 12px);
167
- padding: 3px 10px;
168
- border: 0;
169
- border-radius: var(--border-radius-full, 999px);
289
+ flex-wrap: wrap;
290
+ gap: 6px;
291
+ }
292
+ .segmented button {
293
+ min-height: 34px;
294
+ padding: 0 14px;
295
+ border-radius: var(--pill);
296
+ border: 1px solid var(--line-2);
170
297
  background: transparent;
171
- color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
298
+ font-size: var(--text-sm);
172
299
  cursor: pointer;
173
300
  }
174
- .mode button[aria-pressed="true"] {
175
- background: var(--color-text-primary, #1b1b1a);
176
- color: var(--color-background-primary, #ffffff);
301
+ .segmented button:hover { background: var(--bg-2); }
302
+ .segmented button[aria-pressed="true"] {
303
+ border-color: transparent;
304
+ background: var(--fg);
305
+ color: var(--bg);
306
+ }
307
+ .segmented.small { gap: 2px; padding: 2px; border-radius: var(--pill); background: var(--bg-2); }
308
+ .segmented.small button { min-height: 28px; padding: 0 12px; border: 0; font-size: var(--text-xs); }
309
+ .segmented.small button[aria-pressed="true"] {
310
+ background: var(--bg);
311
+ color: var(--fg);
312
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
177
313
  }
178
314
 
179
315
  /* A gradient: its colours on a track, the one being edited ringed. */
180
316
  .stops {
181
317
  display: flex;
182
318
  align-items: center;
183
- gap: 6px;
319
+ gap: 8px;
184
320
  min-width: 0;
185
321
  }
186
- .stops[hidden] { display: none; }
187
322
  .track {
188
323
  position: relative;
189
- width: 132px;
190
- height: 22px;
191
- border-radius: var(--border-radius-full, 999px);
192
- border: 1px solid rgba(0, 0, 0, 0.18);
193
- box-sizing: border-box;
324
+ flex: 1;
325
+ height: 28px;
326
+ border-radius: var(--pill);
327
+ box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
194
328
  }
195
329
  .stop {
196
330
  position: absolute;
197
331
  top: 50%;
198
- width: 18px;
199
- height: 18px;
200
- margin: -9px 0 0 -9px;
332
+ width: 22px;
333
+ height: 22px;
334
+ margin: -11px 0 0 -11px;
201
335
  padding: 0;
202
336
  border-radius: 50%;
203
- border: 2px solid #ffffff;
204
- box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
337
+ border: 3px solid #ffffff;
338
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
205
339
  cursor: pointer;
206
340
  }
207
- .stop[aria-pressed="true"] {
208
- box-shadow: 0 0 0 2px var(--color-text-primary, #1b1b1a);
341
+ .stop[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--fg), 0 1px 3px rgba(0, 0, 0, 0.3); }
342
+
343
+ /* Fullscreen sections: one heading each, generous air between. */
344
+ .block { display: grid; gap: 14px; }
345
+ :root[data-display-mode="fullscreen"] .block { padding: 20px; }
346
+ #knobs .section { border-top: 1px solid var(--line); }
347
+ .block h3, .section h3 {
348
+ margin: 0;
349
+ font-size: var(--text-xs);
350
+ font-weight: 600;
351
+ letter-spacing: 0.04em;
352
+ text-transform: uppercase;
353
+ color: var(--fg-2);
209
354
  }
355
+ .section { display: grid; gap: 18px; padding: 20px; }
210
356
 
211
- .foot {
357
+ .knob { display: grid; gap: 10px; min-width: 0; }
358
+ .knob-head {
212
359
  display: flex;
360
+ justify-content: space-between;
361
+ gap: 10px;
362
+ font-size: var(--text-sm);
363
+ }
364
+ .knob-head output { color: var(--fg-2); font-variant-numeric: tabular-nums; }
365
+ /* A thin track and a round thumb, the same in every browser. */
366
+ .knob input[type="range"] {
367
+ -webkit-appearance: none;
368
+ appearance: none;
369
+ width: 100%;
370
+ height: 20px;
371
+ margin: 0;
372
+ background: transparent;
373
+ cursor: pointer;
374
+ --fill: 50%;
375
+ }
376
+ .knob input[type="range"]::-webkit-slider-runnable-track {
377
+ height: 4px;
378
+ border-radius: 2px;
379
+ background: linear-gradient(to right, var(--fg) var(--fill), var(--line-2) var(--fill));
380
+ }
381
+ .knob input[type="range"]::-moz-range-track { height: 4px; border-radius: 2px; background: var(--line-2); }
382
+ .knob input[type="range"]::-moz-range-progress { height: 4px; border-radius: 2px; background: var(--fg); }
383
+ .knob input[type="range"]::-webkit-slider-thumb {
384
+ -webkit-appearance: none;
385
+ width: 20px;
386
+ height: 20px;
387
+ margin-top: -8px;
388
+ border-radius: 50%;
389
+ background: #ffffff;
390
+ border: 0;
391
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.2);
392
+ }
393
+ .knob input[type="range"]::-moz-range-thumb {
394
+ width: 20px;
395
+ height: 20px;
396
+ border-radius: 50%;
397
+ background: #ffffff;
398
+ border: 0;
399
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12), 0 2px 6px rgba(0, 0, 0, 0.2);
400
+ }
401
+ details.more > summary {
402
+ list-style: none;
403
+ display: inline-flex;
213
404
  align-items: center;
214
- gap: 8px;
215
- padding: 12px 14px;
405
+ gap: 6px;
406
+ cursor: pointer;
407
+ font-size: var(--text-sm);
408
+ color: var(--fg-2);
216
409
  }
217
- .price {
218
- flex: 1;
219
- min-width: 0;
220
- font-size: var(--font-text-sm-size, 13px);
221
- color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
410
+ details.more > summary::-webkit-details-marker { display: none; }
411
+ details.more > summary::before {
412
+ content: "";
413
+ width: 6px;
414
+ height: 6px;
415
+ border-right: 1.5px solid currentColor;
416
+ border-bottom: 1.5px solid currentColor;
417
+ transform: rotate(-45deg);
418
+ transition: transform 120ms ease;
222
419
  }
420
+ details.more[open] > summary::before { transform: rotate(45deg); }
421
+ details.more[open] > summary { margin-bottom: 18px; }
422
+ details.more > div { display: grid; gap: 18px; }
423
+
424
+ .foot {
425
+ display: flex;
426
+ align-items: center;
427
+ gap: 10px;
428
+ padding: 14px 18px 18px;
429
+ }
430
+ :root[data-display-mode="fullscreen"] .foot { padding: 14px 20px; border-top: 1px solid var(--line); }
431
+ .price { flex: 1; min-width: 0; display: grid; gap: 1px; }
432
+ .price strong { font-size: var(--text-md); font-weight: 600; font-variant-numeric: tabular-nums; }
433
+ .price span { font-size: var(--text-xs); color: var(--fg-2); }
223
434
  button.action {
224
- font: inherit;
225
- font-size: var(--font-text-sm-size, 13px);
226
- font-weight: 600;
227
- border-radius: var(--border-radius-full, 999px);
228
- padding: 8px 14px;
435
+ min-height: 44px;
436
+ border-radius: var(--pill);
437
+ padding: 0 20px;
229
438
  cursor: pointer;
230
- border: 1px solid var(--color-border-primary, rgba(0, 0, 0, 0.14));
439
+ border: 1px solid var(--line-2);
231
440
  background: transparent;
232
- color: var(--color-text-primary, #1b1b1a);
233
- }
234
- button.action.primary {
235
- border-color: transparent;
236
- background: var(--color-text-primary, #1b1b1a);
237
- color: var(--color-background-primary, #ffffff);
441
+ font-size: var(--text-sm);
442
+ font-weight: 600;
443
+ transition: background 120ms ease;
238
444
  }
445
+ button.action:hover:not(:disabled) { background: var(--bg-2); }
446
+ button.action.primary { border-color: transparent; background: var(--fg); color: var(--bg); }
447
+ button.action.primary:hover:not(:disabled) { background: var(--fg); opacity: 0.88; }
239
448
  button.action:disabled { opacity: 0.5; cursor: default; }
449
+ :root:not([data-display-mode="fullscreen"]) .foot button.action { flex: 1; }
450
+ :root[data-display-mode="fullscreen"] #order { flex: 1; max-width: 200px; }
240
451
 
452
+ /* What the card is doing, as a small chip on the render. */
241
453
  .status {
242
- padding: 0 14px 10px;
243
- font-size: var(--font-text-xs-size, 11px);
244
- color: var(--color-text-secondary, rgba(27, 27, 26, 0.62));
454
+ position: absolute;
455
+ top: 12px;
456
+ left: 12px;
457
+ max-width: calc(100% - 24px);
458
+ box-sizing: border-box;
459
+ padding: 6px 12px;
460
+ border-radius: var(--pill);
461
+ background: color-mix(in srgb, var(--bg) 82%, transparent);
462
+ -webkit-backdrop-filter: blur(8px);
463
+ backdrop-filter: blur(8px);
464
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 0 0 1px var(--line);
465
+ font-size: var(--text-xs);
466
+ color: var(--fg);
245
467
  }
246
468
  .status:empty { display: none; }
469
+
470
+ /* Until the design arrives there is nothing to pick from: hold the controls back. */
471
+ .card:not([data-ready]) .needs-design { visibility: hidden; }
472
+ .card:not([data-ready]) button.action { opacity: 0.5; pointer-events: none; }
247
473
  </style>
248
474
  </head>
249
475
  <body>
250
476
  <div class="card" id="card">
477
+ <div class="top full-only">
478
+ <button class="icon-button close" type="button" id="close" aria-label="Close the editor">
479
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>
480
+ </button>
481
+ <strong>Pocket camera</strong>
482
+ <span class="muted">Changes save as you go</span>
483
+ </div>
484
+
251
485
  <div class="stage">
252
486
  <iframe id="viewer" title="3D preview of your camera" allow="fullscreen" hidden></iframe>
253
- <div class="fallback" id="fallback" hidden></div>
487
+ <div class="preview" id="fallback" hidden></div>
488
+ <div class="status" id="status" role="status">Connecting&hellip;</div>
254
489
  </div>
255
490
 
256
- <div id="view-design">
257
- <div class="controls">
258
- <div class="row">
259
- <span>Shell</span>
260
- <div class="mode" role="group" aria-label="Shell finish">
261
- <button type="button" id="solid-shell" aria-pressed="true">Solid</button>
262
- <button type="button" id="gradient-shell" aria-pressed="false">Gradient</button>
491
+ <!-- Inline this is the card; fullscreen, the editor's panel. -->
492
+ <div class="panel" id="view-design">
493
+ <div class="scroll">
494
+ <div class="title inline-only">
495
+ <div>
496
+ <h2>Pocket camera</h2>
497
+ <p class="muted">A working camera in a shell you design</p>
263
498
  </div>
264
- <div class="stops" id="stops-shell" hidden></div>
499
+ <span class="price-tag needs-design" id="price-inline"></span>
265
500
  </div>
266
- <div class="row">
267
- <span></span>
268
- <div class="swatches" id="sw-shell"></div>
269
- <label class="pick" title="Any colour"><input type="color" id="pick-shell" aria-label="Shell: any colour" /></label>
270
- <button class="icon-button" type="button" id="dropper-shell" title="Pick a colour from the screen" aria-label="Shell: pick a colour from the screen" hidden>
271
- <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
272
- </button>
273
- </div>
274
- <div class="row">
275
- <span>Button</span>
276
- <div class="mode" role="group" aria-label="Button finish">
277
- <button type="button" id="solid-button" aria-pressed="true">Solid</button>
278
- <button type="button" id="gradient-button" aria-pressed="false">Gradient</button>
501
+
502
+ <div class="block needs-design">
503
+ <h3 class="full-only">Colour</h3>
504
+ <div class="parts" role="tablist" aria-label="Part to colour">
505
+ <button type="button" role="tab" id="tab-shell" aria-selected="true" aria-controls="group-shell">
506
+ <span class="dot" id="dot-shell"></span>Body
507
+ </button>
508
+ <button type="button" role="tab" id="tab-button" aria-selected="false" aria-controls="group-button">
509
+ <span class="dot" id="dot-button"></span>Shutter
510
+ </button>
511
+ </div>
512
+
513
+ <div class="group" id="group-shell" role="tabpanel" aria-labelledby="tab-shell">
514
+ <div class="group-head">
515
+ <span>Body · <b id="note-shell"></b></span>
516
+ <div class="segmented small full-only" role="group" aria-label="Body finish">
517
+ <button type="button" id="solid-shell" aria-pressed="true">Solid</button>
518
+ <button type="button" id="gradient-shell" aria-pressed="false">Gradient</button>
519
+ </div>
520
+ </div>
521
+ <div class="stops full-only" id="stops-shell" hidden></div>
522
+ <div class="swatches" id="sw-shell"></div>
523
+ </div>
524
+
525
+ <div class="group" id="group-button" role="tabpanel" aria-labelledby="tab-button" hidden>
526
+ <div class="group-head">
527
+ <span>Shutter button · <b id="note-button"></b></span>
528
+ <div class="segmented small full-only" role="group" aria-label="Shutter button finish">
529
+ <button type="button" id="solid-button" aria-pressed="true">Solid</button>
530
+ <button type="button" id="gradient-button" aria-pressed="false">Gradient</button>
531
+ </div>
532
+ </div>
533
+ <div class="stops full-only" id="stops-button" hidden></div>
534
+ <div class="swatches" id="sw-button"></div>
279
535
  </div>
280
- <div class="stops" id="stops-button" hidden></div>
281
- </div>
282
- <div class="row">
283
- <span></span>
284
- <div class="swatches" id="sw-button"></div>
285
- <label class="pick" title="Any colour"><input type="color" id="pick-button" aria-label="Button: any colour" /></label>
286
- <button class="icon-button" type="button" id="dropper-button" title="Pick a colour from the screen" aria-label="Button: pick a colour from the screen" hidden>
287
- <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
288
- </button>
289
536
  </div>
537
+
538
+ <div class="full-only" id="knobs"></div>
290
539
  </div>
291
540
  <div class="foot">
292
- <div class="price" id="price"></div>
293
- <button class="action" id="customize" type="button">Customize</button>
541
+ <div class="price full-only">
542
+ <strong id="price"></strong>
543
+ <span>+ shipping</span>
544
+ </div>
545
+ <button class="action inline-only" id="customize" type="button">Customize</button>
294
546
  <button class="action primary" id="order" type="button">Order</button>
295
547
  </div>
296
548
  </div>
297
-
298
- <div class="status" id="status">Connecting&hellip;</div>
299
549
  </div>
300
550
  <script type="module" src="./main.ts"></script>
301
551
  </body>