pmx-canvas 0.4.1 → 0.4.2

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.
@@ -171,6 +171,281 @@
171
171
  --c-annotation: #ffff00;
172
172
  }
173
173
 
174
+ :root[data-theme="midnight"] {
175
+ /* ── Core palette (midnight · deep indigo night) ─────────── */
176
+ --c-bg: #0A0D1C;
177
+ --c-panel: #12172E;
178
+ --c-panel-soft: #0D1224;
179
+ --c-line: #232B4A;
180
+ --c-text: #E6E9F7;
181
+ --c-text-soft: #C6CCE8;
182
+ --c-muted: #8D96BC;
183
+ --c-dim: #5B6383;
184
+ --c-accent: #8B96FF;
185
+ --c-ok: #34D399;
186
+ --c-warn: #F5C452;
187
+ --c-warn-alt: #FFB300;
188
+ --c-danger: #FF6B8A;
189
+ --c-purple: #B388FF;
190
+ --c-thinking: #B388FF;
191
+ --c-subagent: #22D3EE;
192
+ --c-input-bg: #0D1224;
193
+ --c-dot-grid: rgba(139, 150, 255, 0.08);
194
+ --c-panel-overlay: rgba(10, 13, 28, 0.6);
195
+ --c-panel-glass: rgba(18, 23, 46, 0.92);
196
+ --c-shadow: rgba(0, 0, 0, 0.3);
197
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
198
+ --c-glow-accent: rgba(139, 150, 255, 0.15);
199
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
200
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
201
+ --c-surface-th: rgba(255, 255, 255, 0.04);
202
+ --c-accent-8: rgba(139, 150, 255, 0.08);
203
+ --c-accent-10: rgba(139, 150, 255, 0.1);
204
+ --c-accent-12: rgba(139, 150, 255, 0.12);
205
+ --c-accent-15: rgba(139, 150, 255, 0.15);
206
+ --c-accent-25: rgba(139, 150, 255, 0.25);
207
+ --c-accent-30: rgba(139, 150, 255, 0.3);
208
+ --c-accent-40: rgba(139, 150, 255, 0.4);
209
+ --c-ok-10: rgba(52, 211, 153, 0.1);
210
+ --c-ok-12: rgba(52, 211, 153, 0.12);
211
+ --c-ok-20: rgba(52, 211, 153, 0.2);
212
+ --c-ok-25: rgba(52, 211, 153, 0.25);
213
+ --c-warn-10: rgba(245, 196, 82, 0.1);
214
+ --c-warn-12: rgba(245, 196, 82, 0.12);
215
+ --c-warn-15: rgba(245, 196, 82, 0.15);
216
+ --c-warn-20: rgba(245, 196, 82, 0.2);
217
+ --c-warn-22: rgba(245, 196, 82, 0.22);
218
+ --c-warn-30: rgba(245, 196, 82, 0.3);
219
+ --c-warn-60: rgba(245, 196, 82, 0.6);
220
+ --c-danger-12: rgba(255, 107, 138, 0.12);
221
+ --c-line-30: rgba(35, 43, 74, 0.3);
222
+ --c-contrast-fg: #0A0D1C;
223
+ --c-accent-hover: #A3ACFF;
224
+ --c-warn-hover: #F7D06E;
225
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
226
+ --c-annotation: #EDEDF7;
227
+ }
228
+
229
+ :root[data-theme="sepia"] {
230
+ /* ── Core palette (sepia · warm amber paper) ─────────────── */
231
+ --c-bg: #F2E7D5;
232
+ --c-panel: #EBDCC2;
233
+ --c-panel-soft: #E8D8BC;
234
+ --c-line: #D3C1A0;
235
+ --c-text: #2E2416;
236
+ --c-text-soft: #554833;
237
+ --c-muted: #77664C;
238
+ --c-dim: #978870;
239
+ --c-accent: #B4632C;
240
+ --c-ok: #3E7D4F;
241
+ --c-warn: #A97B1E;
242
+ --c-warn-alt: #8F6A0D;
243
+ --c-danger: #B3402E;
244
+ --c-purple: #8456B0;
245
+ --c-thinking: #8456B0;
246
+ --c-subagent: #0E7C7B;
247
+ --c-input-bg: #F7EEDD;
248
+ --c-dot-grid: rgba(46, 36, 22, 0.08);
249
+ --c-panel-overlay: rgba(242, 231, 213, 0.88);
250
+ --c-panel-glass: rgba(235, 220, 194, 0.88);
251
+ --c-shadow: rgba(46, 36, 22, 0.08);
252
+ --c-shadow-heavy: rgba(46, 36, 22, 0.16);
253
+ --c-glow-accent: rgba(180, 99, 44, 0.12);
254
+ --c-surface-hover: rgba(46, 36, 22, 0.06);
255
+ --c-surface-subtle: rgba(46, 36, 22, 0.03);
256
+ --c-surface-th: rgba(46, 36, 22, 0.04);
257
+ --c-accent-8: rgba(180, 99, 44, 0.08);
258
+ --c-accent-10: rgba(180, 99, 44, 0.1);
259
+ --c-accent-12: rgba(180, 99, 44, 0.12);
260
+ --c-accent-15: rgba(180, 99, 44, 0.15);
261
+ --c-accent-25: rgba(180, 99, 44, 0.25);
262
+ --c-accent-30: rgba(180, 99, 44, 0.3);
263
+ --c-accent-40: rgba(180, 99, 44, 0.4);
264
+ --c-ok-10: rgba(62, 125, 79, 0.1);
265
+ --c-ok-12: rgba(62, 125, 79, 0.12);
266
+ --c-ok-20: rgba(62, 125, 79, 0.2);
267
+ --c-ok-25: rgba(62, 125, 79, 0.25);
268
+ --c-warn-10: rgba(169, 123, 30, 0.1);
269
+ --c-warn-12: rgba(169, 123, 30, 0.12);
270
+ --c-warn-15: rgba(169, 123, 30, 0.15);
271
+ --c-warn-20: rgba(169, 123, 30, 0.2);
272
+ --c-warn-22: rgba(169, 123, 30, 0.22);
273
+ --c-warn-30: rgba(169, 123, 30, 0.3);
274
+ --c-warn-60: rgba(169, 123, 30, 0.6);
275
+ --c-danger-12: rgba(179, 64, 46, 0.12);
276
+ --c-line-30: rgba(211, 193, 160, 0.5);
277
+ --c-contrast-fg: #F2E7D5;
278
+ --c-accent-hover: #C4763F;
279
+ --c-warn-hover: #BD9037;
280
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(46, 36, 22, 0.02));
281
+ --c-annotation: #2E2416;
282
+ }
283
+
284
+ :root[data-theme="arctic"] {
285
+ /* ── Core palette (arctic · cool slate + ice) ────────────── */
286
+ --c-bg: #20242E;
287
+ --c-panel: #2A2F3C;
288
+ --c-panel-soft: #242936;
289
+ --c-line: #3A4152;
290
+ --c-text: #E9EDF4;
291
+ --c-text-soft: #CBD3E1;
292
+ --c-muted: #939DB1;
293
+ --c-dim: #667082;
294
+ --c-accent: #7FC8DE;
295
+ --c-ok: #74C596;
296
+ --c-warn: #E8C476;
297
+ --c-warn-alt: #E0A93F;
298
+ --c-danger: #D9707C;
299
+ --c-purple: #B48EAD;
300
+ --c-thinking: #B48EAD;
301
+ --c-subagent: #6FD6C4;
302
+ --c-input-bg: #242936;
303
+ --c-dot-grid: rgba(127, 200, 222, 0.08);
304
+ --c-panel-overlay: rgba(32, 36, 46, 0.6);
305
+ --c-panel-glass: rgba(42, 47, 60, 0.92);
306
+ --c-shadow: rgba(0, 0, 0, 0.3);
307
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
308
+ --c-glow-accent: rgba(127, 200, 222, 0.15);
309
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
310
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
311
+ --c-surface-th: rgba(255, 255, 255, 0.04);
312
+ --c-accent-8: rgba(127, 200, 222, 0.08);
313
+ --c-accent-10: rgba(127, 200, 222, 0.1);
314
+ --c-accent-12: rgba(127, 200, 222, 0.12);
315
+ --c-accent-15: rgba(127, 200, 222, 0.15);
316
+ --c-accent-25: rgba(127, 200, 222, 0.25);
317
+ --c-accent-30: rgba(127, 200, 222, 0.3);
318
+ --c-accent-40: rgba(127, 200, 222, 0.4);
319
+ --c-ok-10: rgba(116, 197, 150, 0.1);
320
+ --c-ok-12: rgba(116, 197, 150, 0.12);
321
+ --c-ok-20: rgba(116, 197, 150, 0.2);
322
+ --c-ok-25: rgba(116, 197, 150, 0.25);
323
+ --c-warn-10: rgba(232, 196, 118, 0.1);
324
+ --c-warn-12: rgba(232, 196, 118, 0.12);
325
+ --c-warn-15: rgba(232, 196, 118, 0.15);
326
+ --c-warn-20: rgba(232, 196, 118, 0.2);
327
+ --c-warn-22: rgba(232, 196, 118, 0.22);
328
+ --c-warn-30: rgba(232, 196, 118, 0.3);
329
+ --c-warn-60: rgba(232, 196, 118, 0.6);
330
+ --c-danger-12: rgba(217, 112, 124, 0.12);
331
+ --c-line-30: rgba(58, 65, 82, 0.3);
332
+ --c-contrast-fg: #20242E;
333
+ --c-accent-hover: #9AD5E7;
334
+ --c-warn-hover: #EDD194;
335
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
336
+ --c-annotation: #ECEFF4;
337
+ }
338
+
339
+ :root[data-theme="ember"] {
340
+ /* ── Core palette (ember · charcoal + vivid warm orange) ─── */
341
+ --c-bg: #0E0E11;
342
+ --c-panel: #17181D;
343
+ --c-panel-soft: #121317;
344
+ --c-line: #2A2C34;
345
+ --c-text: #ECEDF0;
346
+ --c-text-soft: #C9CCD4;
347
+ --c-muted: #8E939F;
348
+ --c-dim: #5F646E;
349
+ --c-accent: #FF5C2E;
350
+ --c-ok: #4CC38A;
351
+ --c-warn: #F5B944;
352
+ --c-warn-alt: #FFB300;
353
+ --c-danger: #F0566A;
354
+ --c-purple: #A78BFA;
355
+ --c-thinking: #A78BFA;
356
+ --c-subagent: #38BDF8;
357
+ --c-input-bg: #121317;
358
+ --c-dot-grid: rgba(255, 92, 46, 0.08);
359
+ --c-panel-overlay: rgba(14, 14, 17, 0.6);
360
+ --c-panel-glass: rgba(23, 24, 29, 0.92);
361
+ --c-shadow: rgba(0, 0, 0, 0.3);
362
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
363
+ --c-glow-accent: rgba(255, 92, 46, 0.15);
364
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
365
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
366
+ --c-surface-th: rgba(255, 255, 255, 0.04);
367
+ --c-accent-8: rgba(255, 92, 46, 0.08);
368
+ --c-accent-10: rgba(255, 92, 46, 0.1);
369
+ --c-accent-12: rgba(255, 92, 46, 0.12);
370
+ --c-accent-15: rgba(255, 92, 46, 0.15);
371
+ --c-accent-25: rgba(255, 92, 46, 0.25);
372
+ --c-accent-30: rgba(255, 92, 46, 0.3);
373
+ --c-accent-40: rgba(255, 92, 46, 0.4);
374
+ --c-ok-10: rgba(76, 195, 138, 0.1);
375
+ --c-ok-12: rgba(76, 195, 138, 0.12);
376
+ --c-ok-20: rgba(76, 195, 138, 0.2);
377
+ --c-ok-25: rgba(76, 195, 138, 0.25);
378
+ --c-warn-10: rgba(245, 185, 68, 0.1);
379
+ --c-warn-12: rgba(245, 185, 68, 0.12);
380
+ --c-warn-15: rgba(245, 185, 68, 0.15);
381
+ --c-warn-20: rgba(245, 185, 68, 0.2);
382
+ --c-warn-22: rgba(245, 185, 68, 0.22);
383
+ --c-warn-30: rgba(245, 185, 68, 0.3);
384
+ --c-warn-60: rgba(245, 185, 68, 0.6);
385
+ --c-danger-12: rgba(240, 86, 106, 0.12);
386
+ --c-line-30: rgba(42, 44, 52, 0.3);
387
+ --c-contrast-fg: #0E0E11;
388
+ --c-accent-hover: #FF7A52;
389
+ --c-warn-hover: #F7C868;
390
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
391
+ --c-annotation: #F2F2F4;
392
+ }
393
+
394
+ :root[data-theme="forest"] {
395
+ /* ── Core palette (forest · deep green) ──────────────────── */
396
+ --c-bg: #0C1712;
397
+ --c-panel: #14231B;
398
+ --c-panel-soft: #101D16;
399
+ --c-line: #26402F;
400
+ --c-text: #E4EFE7;
401
+ --c-text-soft: #C4D6CA;
402
+ --c-muted: #8BA695;
403
+ --c-dim: #5D7566;
404
+ --c-accent: #5CCF8F;
405
+ --c-ok: #74D99F;
406
+ --c-warn: #E0B84F;
407
+ --c-warn-alt: #D9A421;
408
+ --c-danger: #E57373;
409
+ --c-purple: #A88FE0;
410
+ --c-thinking: #A88FE0;
411
+ --c-subagent: #4FD8CE;
412
+ --c-input-bg: #101D16;
413
+ --c-dot-grid: rgba(92, 207, 143, 0.08);
414
+ --c-panel-overlay: rgba(12, 23, 18, 0.6);
415
+ --c-panel-glass: rgba(20, 35, 27, 0.92);
416
+ --c-shadow: rgba(0, 0, 0, 0.3);
417
+ --c-shadow-heavy: rgba(0, 0, 0, 0.5);
418
+ --c-glow-accent: rgba(92, 207, 143, 0.15);
419
+ --c-surface-hover: rgba(255, 255, 255, 0.06);
420
+ --c-surface-subtle: rgba(255, 255, 255, 0.03);
421
+ --c-surface-th: rgba(255, 255, 255, 0.04);
422
+ --c-accent-8: rgba(92, 207, 143, 0.08);
423
+ --c-accent-10: rgba(92, 207, 143, 0.1);
424
+ --c-accent-12: rgba(92, 207, 143, 0.12);
425
+ --c-accent-15: rgba(92, 207, 143, 0.15);
426
+ --c-accent-25: rgba(92, 207, 143, 0.25);
427
+ --c-accent-30: rgba(92, 207, 143, 0.3);
428
+ --c-accent-40: rgba(92, 207, 143, 0.4);
429
+ --c-ok-10: rgba(116, 217, 159, 0.1);
430
+ --c-ok-12: rgba(116, 217, 159, 0.12);
431
+ --c-ok-20: rgba(116, 217, 159, 0.2);
432
+ --c-ok-25: rgba(116, 217, 159, 0.25);
433
+ --c-warn-10: rgba(224, 184, 79, 0.1);
434
+ --c-warn-12: rgba(224, 184, 79, 0.12);
435
+ --c-warn-15: rgba(224, 184, 79, 0.15);
436
+ --c-warn-20: rgba(224, 184, 79, 0.2);
437
+ --c-warn-22: rgba(224, 184, 79, 0.22);
438
+ --c-warn-30: rgba(224, 184, 79, 0.3);
439
+ --c-warn-60: rgba(224, 184, 79, 0.6);
440
+ --c-danger-12: rgba(229, 115, 115, 0.12);
441
+ --c-line-30: rgba(38, 64, 47, 0.3);
442
+ --c-contrast-fg: #0C1712;
443
+ --c-accent-hover: #7BDAA4;
444
+ --c-warn-hover: #E8C871;
445
+ --c-canvas-wash: linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0));
446
+ --c-annotation: #EDF5EF;
447
+ }
448
+
174
449
  * {
175
450
  box-sizing: border-box;
176
451
  margin: 0;
@@ -669,6 +944,7 @@ body,
669
944
  }
670
945
 
671
946
  .toolbar-group {
947
+ position: relative;
672
948
  display: flex;
673
949
  flex-wrap: wrap;
674
950
  align-items: center;
@@ -681,6 +957,127 @@ body,
681
957
  display: block;
682
958
  }
683
959
 
960
+ /* ── Toolbar popover menus (theme picker + narrow-screen More menu) ── */
961
+ .toolbar-menu {
962
+ position: absolute;
963
+ top: calc(100% + 8px);
964
+ right: 0;
965
+ z-index: 1200;
966
+ display: flex;
967
+ flex-direction: column;
968
+ gap: 2px;
969
+ min-width: 224px;
970
+ max-height: min(70vh, 560px);
971
+ overflow-y: auto;
972
+ padding: 6px;
973
+ background: var(--c-panel-glass);
974
+ backdrop-filter: blur(12px);
975
+ border: 1px solid var(--c-line);
976
+ border-radius: var(--radius);
977
+ box-shadow: 0 18px 48px var(--c-shadow-heavy);
978
+ }
979
+
980
+ .toolbar-menu-item {
981
+ display: flex;
982
+ align-items: center;
983
+ gap: 10px;
984
+ width: 100%;
985
+ min-height: 40px;
986
+ padding: 8px 12px;
987
+ background: none;
988
+ border: 1px solid transparent;
989
+ border-radius: var(--radius-sm);
990
+ color: var(--c-text-soft);
991
+ font: inherit;
992
+ font-size: 13px;
993
+ text-align: left;
994
+ cursor: pointer;
995
+ }
996
+
997
+ .toolbar-menu-item:hover {
998
+ background: var(--c-surface-hover);
999
+ color: var(--c-text);
1000
+ border-color: var(--c-line);
1001
+ }
1002
+
1003
+ .toolbar-menu-item.active {
1004
+ color: var(--c-accent);
1005
+ }
1006
+
1007
+ .toolbar-menu-item svg {
1008
+ flex-shrink: 0;
1009
+ }
1010
+
1011
+ .toolbar-menu-check {
1012
+ margin-left: auto;
1013
+ color: var(--c-accent);
1014
+ }
1015
+
1016
+ .toolbar-menu-separator {
1017
+ height: 1px;
1018
+ margin: 4px 8px;
1019
+ background: var(--c-line);
1020
+ flex-shrink: 0;
1021
+ }
1022
+
1023
+ .toolbar-menu-heading {
1024
+ padding: 6px 12px 2px;
1025
+ font-size: 10px;
1026
+ letter-spacing: 0.08em;
1027
+ text-transform: uppercase;
1028
+ color: var(--c-dim);
1029
+ }
1030
+
1031
+ .theme-swatch {
1032
+ display: inline-flex;
1033
+ align-items: center;
1034
+ justify-content: center;
1035
+ width: 18px;
1036
+ height: 18px;
1037
+ border: 1px solid var(--c-line);
1038
+ border-radius: 50%;
1039
+ flex-shrink: 0;
1040
+ }
1041
+
1042
+ .theme-swatch-dot {
1043
+ width: 7px;
1044
+ height: 7px;
1045
+ border-radius: 50%;
1046
+ }
1047
+
1048
+ .toolbar-more-glyph {
1049
+ display: block;
1050
+ font-size: 16px;
1051
+ font-weight: 700;
1052
+ line-height: 1;
1053
+ }
1054
+
1055
+ /* Wide screens: the full two-bar toolbar; the More trigger stays hidden. */
1056
+ .toolbar-collapsible {
1057
+ display: contents;
1058
+ }
1059
+
1060
+ .toolbar-more {
1061
+ display: none;
1062
+ }
1063
+
1064
+ /* Narrow screens / embedded panels: ONE compact bar (brand, status, view
1065
+ controls) — every secondary control folds into the More menu instead of
1066
+ wrapping into a second toolbar row. */
1067
+ @media (max-width: 900px) {
1068
+ .toolbar-collapsible {
1069
+ display: none;
1070
+ }
1071
+
1072
+ .toolbar-actions-bar {
1073
+ display: none;
1074
+ }
1075
+
1076
+ .toolbar-more {
1077
+ display: contents;
1078
+ }
1079
+ }
1080
+
684
1081
  @media (max-width: 900px) {
685
1082
  .toolbar-tooltip {
686
1083
  min-width: 150px;
@@ -121,6 +121,166 @@
121
121
  --color-danger: #ff0000;
122
122
  }
123
123
 
124
+ :root[data-theme="midnight"] {
125
+ /* ── Core palette (midnight · deep indigo night) ─────────── */
126
+ --c-bg: #0A0D1C;
127
+ --c-panel: #12172E;
128
+ --c-panel-soft: #0D1224;
129
+ --c-line: #232B4A;
130
+ --c-text: #E6E9F7;
131
+ --c-text-soft: #C6CCE8;
132
+ --c-muted: #8D96BC;
133
+ --c-dim: #5B6383;
134
+ --c-accent: #8B96FF;
135
+ --c-ok: #34D399;
136
+ --c-warn: #F5C452;
137
+ --c-warn-alt: #FFB300;
138
+ --c-danger: #FF6B8A;
139
+ --c-purple: #B388FF;
140
+
141
+ --color-bg: #0A0D1C;
142
+ --color-panel: #12172E;
143
+ --color-surface: #0D1224;
144
+ --color-border: #232B4A;
145
+ --color-text: #E6E9F7;
146
+ --color-text-primary: #E6E9F7;
147
+ --color-text-secondary: #C6CCE8;
148
+ --color-text-muted: #8D96BC;
149
+ --color-text-dim: #5B6383;
150
+ --color-accent: #8B96FF;
151
+ --color-success: #34D399;
152
+ --color-warning: #F5C452;
153
+ --color-danger: #FF6B8A;
154
+ }
155
+
156
+ :root[data-theme="sepia"] {
157
+ /* ── Core palette (sepia · warm amber paper) ─────────────── */
158
+ --c-bg: #F2E7D5;
159
+ --c-panel: #EBDCC2;
160
+ --c-panel-soft: #E8D8BC;
161
+ --c-line: #D3C1A0;
162
+ --c-text: #2E2416;
163
+ --c-text-soft: #554833;
164
+ --c-muted: #77664C;
165
+ --c-dim: #978870;
166
+ --c-accent: #B4632C;
167
+ --c-ok: #3E7D4F;
168
+ --c-warn: #A97B1E;
169
+ --c-warn-alt: #8F6A0D;
170
+ --c-danger: #B3402E;
171
+ --c-purple: #8456B0;
172
+
173
+ --color-bg: #F2E7D5;
174
+ --color-panel: #EBDCC2;
175
+ --color-surface: #E8D8BC;
176
+ --color-border: #D3C1A0;
177
+ --color-text: #2E2416;
178
+ --color-text-primary: #2E2416;
179
+ --color-text-secondary: #554833;
180
+ --color-text-muted: #77664C;
181
+ --color-text-dim: #978870;
182
+ --color-accent: #B4632C;
183
+ --color-success: #3E7D4F;
184
+ --color-warning: #A97B1E;
185
+ --color-danger: #B3402E;
186
+ }
187
+
188
+ :root[data-theme="arctic"] {
189
+ /* ── Core palette (arctic · cool slate + ice) ────────────── */
190
+ --c-bg: #20242E;
191
+ --c-panel: #2A2F3C;
192
+ --c-panel-soft: #242936;
193
+ --c-line: #3A4152;
194
+ --c-text: #E9EDF4;
195
+ --c-text-soft: #CBD3E1;
196
+ --c-muted: #939DB1;
197
+ --c-dim: #667082;
198
+ --c-accent: #7FC8DE;
199
+ --c-ok: #74C596;
200
+ --c-warn: #E8C476;
201
+ --c-warn-alt: #E0A93F;
202
+ --c-danger: #D9707C;
203
+ --c-purple: #B48EAD;
204
+
205
+ --color-bg: #20242E;
206
+ --color-panel: #2A2F3C;
207
+ --color-surface: #242936;
208
+ --color-border: #3A4152;
209
+ --color-text: #E9EDF4;
210
+ --color-text-primary: #E9EDF4;
211
+ --color-text-secondary: #CBD3E1;
212
+ --color-text-muted: #939DB1;
213
+ --color-text-dim: #667082;
214
+ --color-accent: #7FC8DE;
215
+ --color-success: #74C596;
216
+ --color-warning: #E8C476;
217
+ --color-danger: #D9707C;
218
+ }
219
+
220
+ :root[data-theme="ember"] {
221
+ /* ── Core palette (ember · charcoal + vivid warm orange) ─── */
222
+ --c-bg: #0E0E11;
223
+ --c-panel: #17181D;
224
+ --c-panel-soft: #121317;
225
+ --c-line: #2A2C34;
226
+ --c-text: #ECEDF0;
227
+ --c-text-soft: #C9CCD4;
228
+ --c-muted: #8E939F;
229
+ --c-dim: #5F646E;
230
+ --c-accent: #FF5C2E;
231
+ --c-ok: #4CC38A;
232
+ --c-warn: #F5B944;
233
+ --c-warn-alt: #FFB300;
234
+ --c-danger: #F0566A;
235
+ --c-purple: #A78BFA;
236
+
237
+ --color-bg: #0E0E11;
238
+ --color-panel: #17181D;
239
+ --color-surface: #121317;
240
+ --color-border: #2A2C34;
241
+ --color-text: #ECEDF0;
242
+ --color-text-primary: #ECEDF0;
243
+ --color-text-secondary: #C9CCD4;
244
+ --color-text-muted: #8E939F;
245
+ --color-text-dim: #5F646E;
246
+ --color-accent: #FF5C2E;
247
+ --color-success: #4CC38A;
248
+ --color-warning: #F5B944;
249
+ --color-danger: #F0566A;
250
+ }
251
+
252
+ :root[data-theme="forest"] {
253
+ /* ── Core palette (forest · deep green) ──────────────────── */
254
+ --c-bg: #0C1712;
255
+ --c-panel: #14231B;
256
+ --c-panel-soft: #101D16;
257
+ --c-line: #26402F;
258
+ --c-text: #E4EFE7;
259
+ --c-text-soft: #C4D6CA;
260
+ --c-muted: #8BA695;
261
+ --c-dim: #5D7566;
262
+ --c-accent: #5CCF8F;
263
+ --c-ok: #74D99F;
264
+ --c-warn: #E0B84F;
265
+ --c-warn-alt: #D9A421;
266
+ --c-danger: #E57373;
267
+ --c-purple: #A88FE0;
268
+
269
+ --color-bg: #0C1712;
270
+ --color-panel: #14231B;
271
+ --color-surface: #101D16;
272
+ --color-border: #26402F;
273
+ --color-text: #E4EFE7;
274
+ --color-text-primary: #E4EFE7;
275
+ --color-text-secondary: #C4D6CA;
276
+ --color-text-muted: #8BA695;
277
+ --color-text-dim: #5D7566;
278
+ --color-accent: #5CCF8F;
279
+ --color-success: #74D99F;
280
+ --color-warning: #E0B84F;
281
+ --color-danger: #E57373;
282
+ }
283
+
124
284
  html,
125
285
  body {
126
286
  margin: 0;
@@ -1,3 +1,5 @@
1
+ import type { CanvasThemeName } from '../shared/themes.js';
2
+
1
3
  export interface ViewportState {
2
4
  x: number;
3
5
  y: number;
@@ -108,7 +110,7 @@ export function isExcalidrawNode(node: CanvasNodeState): boolean {
108
110
 
109
111
  export interface CanvasLayout {
110
112
  viewport: ViewportState;
111
- theme?: 'dark' | 'light' | 'high-contrast';
113
+ theme?: CanvasThemeName;
112
114
  nodes: CanvasNodeState[];
113
115
  edges: CanvasEdge[];
114
116
  annotations?: CanvasAnnotation[];
@@ -9,6 +9,7 @@ import { Database } from 'bun:sqlite';
9
9
  import { existsSync, mkdirSync } from 'node:fs';
10
10
  import { dirname } from 'node:path';
11
11
  import { gzipSync, gunzipSync } from 'node:zlib';
12
+ import { type CanvasThemeName, normalizeCanvasThemeName } from '../shared/themes.js';
12
13
  import type {
13
14
  CanvasAnnotation,
14
15
  CanvasEdge,
@@ -42,10 +43,10 @@ import {
42
43
 
43
44
  const SCHEMA_VERSION = 1;
44
45
 
45
- export type CanvasTheme = 'dark' | 'light' | 'high-contrast';
46
+ export type CanvasTheme = CanvasThemeName;
46
47
 
47
48
  export function normalizeCanvasTheme(value: unknown, fallback: CanvasTheme = 'dark'): CanvasTheme {
48
- return value === 'dark' || value === 'light' || value === 'high-contrast' ? value : fallback;
49
+ return normalizeCanvasThemeName(value, fallback);
49
50
  }
50
51
 
51
52
  const SCHEMA_SQL = `
@@ -21,8 +21,9 @@ import {
21
21
  HTML_SURFACE_PUSH_SOURCE,
22
22
  } from '../shared/ax-surface-protocol.js';
23
23
  import { contentHeightReporterTag } from '../shared/content-height-reporter.js';
24
+ import { type CanvasThemeName, normalizeCanvasThemeName } from '../shared/themes.js';
24
25
 
25
- export type SurfaceTheme = 'dark' | 'light' | 'high-contrast';
26
+ export type SurfaceTheme = CanvasThemeName;
26
27
 
27
28
  /** Path the surface document links for its theme tokens (served from dist/canvas). */
28
29
  export const SURFACE_THEME_STYLESHEET = '/canvas/surface-theme.css';
@@ -31,7 +32,7 @@ export const SURFACE_THEME_STYLESHEET = '/canvas/surface-theme.css';
31
32
  export const HTML_SURFACE_SANDBOX = 'allow-scripts';
32
33
 
33
34
  export function normalizeSurfaceTheme(value: string | null | undefined): SurfaceTheme {
34
- return value === 'light' || value === 'high-contrast' ? value : 'dark';
35
+ return normalizeCanvasThemeName(value);
35
36
  }
36
37
 
37
38
  /**
@@ -235,6 +236,12 @@ export interface HtmlSurfaceOptions {
235
236
  axState?: unknown;
236
237
  /** Nonce for the content-height reporter (lets the node grow to fit content). */
237
238
  contentHeightToken?: string;
239
+ /**
240
+ * Contents of surface-theme.css to inline as a <style> instead of the
241
+ * same-origin <link> — required for srcdoc-rendered surfaces (nested-iframe
242
+ * hosts) where the stylesheet subresource may not load.
243
+ */
244
+ inlineThemeCss?: string;
238
245
  }
239
246
 
240
247
  /**
@@ -243,7 +250,14 @@ export interface HtmlSurfaceOptions {
243
250
  */
244
251
  export function buildHtmlSurfaceDocument(userHtml: string, options: HtmlSurfaceOptions): string {
245
252
  const themeToken = sanitizeToken(options.themeToken);
246
- const link = `<link rel="stylesheet" href="${SURFACE_THEME_STYLESHEET}">`;
253
+ // Inline the theme tokens when the caller supplies the stylesheet content —
254
+ // in srcdoc-mode embeds (Amp orb portals) the surface document is rendered
255
+ // inline and a <link> subresource may not load, leaving the surface unstyled
256
+ // (dark-on-dark reads as blank). The <link> stays as the fallback for callers
257
+ // without the content.
258
+ const link = options.inlineThemeCss
259
+ ? `<style data-pmx-surface-theme>${options.inlineThemeCss.replace(/<\//g, '<\\/')}</style>`
260
+ : `<link rel="stylesheet" href="${SURFACE_THEME_STYLESHEET}">`;
247
261
  const themeBridge = buildThemeBridge(themeToken);
248
262
  const presentationBridge = options.presentation
249
263
  ? buildPresentationEscapeBridge(sanitizeToken(options.presentationExitToken))
@@ -28,7 +28,12 @@ const themeGetOperation = defineOperation<z.infer<typeof emptySchema>, Record<st
28
28
  });
29
29
 
30
30
  const themeSetShape = {
31
- theme: z.unknown().optional().describe('Theme: dark, light, or high-contrast'),
31
+ theme: z
32
+ .unknown()
33
+ .optional()
34
+ .describe(
35
+ 'Theme name from the canvas theme registry (e.g. dark, light, high-contrast, midnight, sepia, arctic, ember, forest)',
36
+ ),
32
37
  };
33
38
  const themeSetSchema = z.looseObject(themeSetShape);
34
39