@machfivetechchicago/machvive-webmcp-ai 5.6.1 → 5.7.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/README.md CHANGED
@@ -29,6 +29,8 @@ elements with Shadow DOM that work anywhere `customElements` does.
29
29
  | WebMCP analytics | `<machvive-webmcp-analytics>` | Captures every tool call for listing, editing, export, replay, and dataLayer |
30
30
  | Lorum Ipsum | `<machvive-lorum-ipsum>` | Placeholder copy that projects slotted content |
31
31
 
32
+ <img width="1098" height="894" alt="m5t_machvive_ai_webmcp" src="https://github.com/user-attachments/assets/a1433ff0-f8a9-46fb-ad59-735aed155ff8" />
33
+
32
34
  ## ⚡ Integration with Vite (Vanilla JS)
33
35
 
34
36
  This package is optimized for modern ESM environments like Vite. You can import individual components or the entire library at once.
@@ -206,6 +208,52 @@ Each call then pushes `{ event: 'webmcp_tool_call', webmcp_tool, webmcp_status,
206
208
  webmcp_duration_ms, webmcp_params }`. Without the attribute, push individual entries
207
209
  on demand with `callLog.pushToDataLayer(id)` or the per-entry button in the UI.
208
210
 
211
+ ## 🌓 Theming
212
+
213
+ The UI components follow the viewer's OS preference automatically — no
214
+ configuration, no flash of the wrong palette. Set `theme` to override:
215
+
216
+ ```html
217
+ <machvive-webmcp-inspect></machvive-webmcp-inspect> <!-- follows the OS -->
218
+ <machvive-webmcp-analytics theme="dark"></machvive-webmcp-analytics>
219
+ <machvive-webmcp-inspect theme="light"></machvive-webmcp-inspect>
220
+ ```
221
+
222
+ `theme` is also a reflected property, so `el.theme = 'dark'` and
223
+ `el.theme = null` (back to following the OS) work from script.
224
+
225
+ ### Overriding the palette
226
+
227
+ Colors are CSS custom properties on the host. Custom properties inherit *through*
228
+ shadow boundaries where ordinary styles do not, so you can restyle the components
229
+ from your own stylesheet without `::part` or `!important`:
230
+
231
+ ```css
232
+ machvive-webmcp-inspect,
233
+ machvive-webmcp-analytics {
234
+ --mv-accent: #7c3aed;
235
+ --mv-bg: #ffffff;
236
+ --mv-fg: #111827;
237
+ --mv-border: #e5e7eb;
238
+ }
239
+ ```
240
+
241
+ Every token has both a light and a dark value; overriding one replaces it in both
242
+ themes unless you scope your override inside your own media query.
243
+
244
+ | Token | Role |
245
+ | --- | --- |
246
+ | `--mv-fg` / `--mv-muted` / `--mv-faint` | Text: primary, secondary, hints |
247
+ | `--mv-bg` / `--mv-surface` / `--mv-input-bg` | Panel, raised areas, form fields |
248
+ | `--mv-border` / `--mv-border-soft` / `--mv-control-border` | Outlines, dividers, controls |
249
+ | `--mv-hover` / `--mv-selected` | Interactive states |
250
+ | `--mv-accent` / `--mv-accent-fg` | Primary button |
251
+ | `--mv-ok-bg` / `--mv-ok-fg` / `--mv-err-bg` / `--mv-err-fg` | Status badges |
252
+ | `--mv-danger` / `--mv-danger-border` | Destructive actions |
253
+
254
+ Every combination of OS preference and `theme` is verified to meet WCAG AA
255
+ (≥ 4.5:1) across the full UI. If you override tokens, re-check your own contrast.
256
+
209
257
  ## TypeScript
210
258
 
211
259
  Declarations ship with the package — no `@types/*` needed. Importing a component
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@machfivetechchicago/machvive-webmcp-ai",
3
- "version": "5.6.1",
3
+ "version": "5.7.0",
4
4
  "description": "I don't want to use your product's agent; I want my agent to be able to use your product.",
5
5
  "keywords": [
6
6
  "WebMCP",
@@ -1,5 +1,8 @@
1
1
  /** Placeholder-copy element. Projects light DOM through a slot. */
2
- export class MachviveLorumIpsum extends HTMLElement {}
2
+ export class MachviveLorumIpsum extends HTMLElement {
3
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
4
+ theme: 'light' | 'dark' | null;
5
+ }
3
6
 
4
7
  declare global {
5
8
  interface HTMLElementTagNameMap {
@@ -4,10 +4,26 @@ export class MachviveLorumIpsum extends HTMLElement {
4
4
  this.attachShadow({ mode: 'open' });
5
5
  }
6
6
 
7
+
8
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
9
+ get theme() {
10
+ return this.getAttribute('theme');
11
+ }
12
+
13
+ set theme(value) {
14
+ if (value == null) this.removeAttribute('theme');
15
+ else this.setAttribute('theme', value);
16
+ }
17
+
7
18
  connectedCallback() {
8
19
  this.shadowRoot.innerHTML = `
9
20
  <style>
10
21
  :host { display: block; font-family: sans-serif; color: #333; }
22
+ @media (prefers-color-scheme: dark) {
23
+ :host(:not([theme="light"])) { color: #e8eaed; }
24
+ }
25
+ :host([theme="dark"]) { color: #e8eaed; }
26
+ :host([theme="light"]) { color: #333; }
11
27
  </style>
12
28
  <div>
13
29
  <slot>Lorem ipsum dolor sit amet...</slot>
@@ -68,6 +68,8 @@ export function installAnalytics(log?: CallLog): boolean;
68
68
 
69
69
  export class MachviveWebmcpAnalytics extends HTMLElement {
70
70
  readonly log: CallLog;
71
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
72
+ theme: 'light' | 'dark' | null;
71
73
  }
72
74
 
73
75
  declare global {
@@ -13,6 +13,7 @@
13
13
  // exactly what capturing every call requires — finds no registry to instrument
14
14
  // and silently captures nothing.
15
15
  import '../machvive-webmcp-polyfill/machvive-webmcp-polyfill.js';
16
+ import { THEME_CSS } from '../shared/theme.js';
16
17
 
17
18
  export const CALL_EVENT = 'machvive-webmcp-call';
18
19
  export const DB_NAME = 'machvive-webmcp';
@@ -368,40 +369,45 @@ export function installAnalytics(log = callLog) {
368
369
  }
369
370
 
370
371
  const STYLES = `
371
- /* This widget paints a light palette explicitly. Declaring the scheme keeps
372
- UA-rendered parts (controls, scrollbars) light too, instead of the browser
373
- handing form controls dark-mode defaults that vanish on these backgrounds. */
374
- :host { display: block; font: 13px/1.5 system-ui, sans-serif; color: #1a1a1a;
375
- color-scheme: light; }
372
+ ${THEME_CSS}
373
+
374
+ /* Painting the background is not optional: this component sets its own text
375
+ colour per theme, so it cannot rely on inheriting a compatible surface from
376
+ whatever page embeds it. */
377
+ :host { display: block; font: 13px/1.5 system-ui, sans-serif;
378
+ color: var(--mv-fg); background: var(--mv-bg); }
376
379
  :host([hidden]) { display: none; }
377
380
  .bar { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-bottom: 8px; }
378
381
  .count { font-weight: 600; margin-right: auto; }
379
382
  /* color is required, not decorative: form controls do not inherit it, so
380
383
  without this the UA picks one per theme and white-on-white can result. */
381
- button { font: inherit; color: #1a1a1a; padding: 3px 9px; border: 1px solid #ccc;
382
- border-radius: 4px; background: #fff; cursor: pointer; }
383
- button:hover { background: #f2f2f2; }
384
- button.danger { color: #a01; border-color: #d8a0a0; }
385
- ol { list-style: none; margin: 0; padding: 0; border: 1px solid #e2e2e2; border-radius: 6px;
386
- max-height: 380px; overflow-y: auto; }
387
- li { border-bottom: 1px solid #eee; }
384
+ button { font: inherit; color: var(--mv-fg); padding: 3px 9px;
385
+ border: 1px solid var(--mv-control-border); border-radius: 4px;
386
+ background: var(--mv-bg); cursor: pointer; }
387
+ button:hover { background: var(--mv-hover); }
388
+ button.danger { color: var(--mv-danger); border-color: var(--mv-danger-border); }
389
+ ol { list-style: none; margin: 0; padding: 0; border: 1px solid var(--mv-border);
390
+ border-radius: 6px; max-height: 380px; overflow-y: auto; background: var(--mv-bg); }
391
+ li { border-bottom: 1px solid var(--mv-border-soft); }
388
392
  li:last-child { border-bottom: 0; }
389
393
  .row { display: flex; gap: 8px; align-items: center; padding: 6px 10px; cursor: pointer; }
390
- .row:hover { background: #fafafa; }
394
+ .row:hover { background: var(--mv-hover); }
391
395
  .tool { font-family: ui-monospace, monospace; font-weight: 600; }
392
396
  .status { font-size: 11px; padding: 1px 6px; border-radius: 10px; }
393
- .status.ok { background: #e6f4ea; color: #137333; }
394
- .status.error { background: #fce8e6; color: #c5221f; }
395
- .ms { color: #777; font-size: 11px; margin-left: auto; }
396
- .detail { padding: 8px 10px; background: #fbfbfb; border-top: 1px solid #eee; }
397
- .detail label { display: block; font-size: 11px; color: #666; margin: 6px 0 2px; }
397
+ .status.ok { background: var(--mv-ok-bg); color: var(--mv-ok-fg); }
398
+ .status.error { background: var(--mv-err-bg); color: var(--mv-err-fg); }
399
+ .ms { color: var(--mv-muted); font-size: 11px; margin-left: auto; }
400
+ .detail { padding: 8px 10px; background: var(--mv-surface);
401
+ border-top: 1px solid var(--mv-border-soft); }
402
+ .detail label { display: block; font-size: 11px; color: var(--mv-muted); margin: 6px 0 2px; }
398
403
  textarea { width: 100%; box-sizing: border-box; font-family: ui-monospace, monospace;
399
- font-size: 12px; color: #1a1a1a; background: #fff; border: 1px solid #ddd;
400
- border-radius: 4px; padding: 5px; }
401
- pre { margin: 0; padding: 6px; color: #1a1a1a; background: #fff; border: 1px solid #eee; border-radius: 4px;
404
+ font-size: 12px; color: var(--mv-fg); background: var(--mv-input-bg);
405
+ border: 1px solid var(--mv-control-border); border-radius: 4px; padding: 5px; }
406
+ pre { margin: 0; padding: 6px; color: var(--mv-fg); background: var(--mv-bg);
407
+ border: 1px solid var(--mv-border-soft); border-radius: 4px;
402
408
  font-size: 12px; overflow-x: auto; white-space: pre-wrap; word-break: break-word; }
403
- .empty { padding: 20px; text-align: center; color: #888; }
404
- .err { color: #c5221f; }
409
+ .empty { padding: 20px; text-align: center; color: var(--mv-faint); }
410
+ .err { color: var(--mv-err-fg); }
405
411
  `;
406
412
 
407
413
  export class MachviveWebmcpAnalytics extends HTMLElement {
@@ -410,7 +416,7 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
410
416
  #onChange = () => this.#render();
411
417
 
412
418
  static get observedAttributes() {
413
- return ['datalayer'];
419
+ return ['datalayer', 'theme'];
414
420
  }
415
421
 
416
422
  constructor() {
@@ -433,6 +439,17 @@ export class MachviveWebmcpAnalytics extends HTMLElement {
433
439
  globalThis.window.removeEventListener(CALL_EVENT, this.#maybeAutoPush);
434
440
  }
435
441
 
442
+
443
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
444
+ get theme() {
445
+ return this.getAttribute('theme');
446
+ }
447
+
448
+ set theme(value) {
449
+ if (value == null) this.removeAttribute('theme');
450
+ else this.setAttribute('theme', value);
451
+ }
452
+
436
453
  /** The shared call log, for page code that wants direct access. */
437
454
  get log() {
438
455
  return this.#log;
@@ -2,7 +2,11 @@
2
2
  * Lists registered WebMCP tools, builds a form from each tool's inputSchema,
3
3
  * and executes it. Inline by default; `floating` docks it as an overlay.
4
4
  */
5
+ export type MachviveTheme = 'light' | 'dark' | null;
6
+
5
7
  export class MachviveWebmcpInspect extends HTMLElement {
8
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
9
+ theme: MachviveTheme;
6
10
  /** Opens the panel (floating mode only). */
7
11
  show(): void;
8
12
  /** Closes the panel (floating mode only). */
@@ -9,6 +9,7 @@
9
9
  * invocation (`callTool`) are additions the bare spec does not provide.
10
10
  */
11
11
  import { TOOLS_CHANGED_EVENT } from '../machvive-webmcp-polyfill/machvive-webmcp-polyfill.js';
12
+ import { THEME_CSS } from '../shared/theme.js';
12
13
 
13
14
  /** Reads a form control back as the JSON type its schema calls for. */
14
15
  function readControl(input, schema = {}) {
@@ -31,71 +32,78 @@ function readControl(input, schema = {}) {
31
32
  }
32
33
 
33
34
  const STYLES = `
34
- /* This widget paints a light palette explicitly. Declaring the scheme keeps
35
- UA-rendered parts (controls, scrollbars) light too, instead of the browser
36
- handing form controls dark-mode defaults that vanish on these backgrounds. */
37
- :host { display: block; font: 13px/1.5 system-ui, sans-serif; color: #1a1a1a;
38
- color-scheme: light; }
35
+ ${THEME_CSS}
36
+
37
+ /* See analytics: a component that themes its own text must paint its own
38
+ surface rather than assume the embedding page supplies a matching one. */
39
+ :host { display: block; font: 13px/1.5 system-ui, sans-serif;
40
+ color: var(--mv-fg); background: var(--mv-bg); }
39
41
  :host([hidden]) { display: none; }
40
42
 
41
43
  /* Floating mode docks the panel without disturbing page layout. */
44
+ /* Floating mode is a detached panel: the .panel and .fab paint themselves, so
45
+ the host must stay transparent or it draws a block over the page. */
42
46
  :host([floating]) { position: fixed; right: 16px; bottom: 16px; z-index: 2147483000;
43
- display: block; width: auto; }
47
+ display: block; width: auto; background: transparent; }
44
48
  :host([floating]) .panel { display: none; width: min(420px, calc(100vw - 32px));
45
49
  max-height: min(70vh, 560px); overflow: auto;
46
- box-shadow: 0 8px 28px rgba(0,0,0,.18); background: #fff; }
50
+ box-shadow: 0 8px 28px var(--mv-shadow); background: var(--mv-bg); }
47
51
  :host([floating][open]) .panel { display: block; }
48
52
  :host([floating]) .fab { display: inline-flex; }
49
53
  .fab { display: none; align-items: center; gap: 6px; margin-top: 8px; float: right;
50
- padding: 7px 13px; border-radius: 999px; border: 1px solid #ccc; background: #fff;
51
- color: #1a1a1a; cursor: pointer; font: inherit; box-shadow: 0 2px 8px rgba(0,0,0,.14); }
54
+ padding: 7px 13px; border-radius: 999px; border: 1px solid var(--mv-control-border);
55
+ background: var(--mv-bg); color: var(--mv-fg); cursor: pointer; font: inherit;
56
+ box-shadow: 0 2px 8px var(--mv-shadow); }
52
57
 
53
- .panel { border: 1px solid #e2e2e2; border-radius: 8px; overflow: hidden; }
58
+ .panel { border: 1px solid var(--mv-border); border-radius: 8px; overflow: hidden;
59
+ background: var(--mv-bg); }
54
60
  header { display: flex; align-items: center; gap: 8px; padding: 7px 10px;
55
- background: #fafafa; color: #1a1a1a; border-bottom: 1px solid #eee; font-weight: 600; }
61
+ background: var(--mv-surface); color: var(--mv-fg);
62
+ border-bottom: 1px solid var(--mv-border-soft); font-weight: 600; }
56
63
  header .close { margin-left: auto; border: 0; background: none; cursor: pointer;
57
- font-size: 16px; line-height: 1; color: #666; }
64
+ font-size: 16px; line-height: 1; color: var(--mv-muted); }
58
65
  :host(:not([floating])) header .close { display: none; }
59
66
 
60
67
  .body { display: flex; min-height: 150px; }
61
68
  @media (max-width: 520px) { .body { flex-direction: column; } }
62
69
 
63
70
  .tools { flex: 0 1 auto; min-width: 120px; max-width: 200px;
64
- border-right: 1px solid #eee; overflow-y: auto; }
71
+ border-right: 1px solid var(--mv-border-soft); overflow-y: auto; }
65
72
  @media (max-width: 520px) { .tools { flex: none; max-width: none; border-right: 0;
66
- border-bottom: 1px solid #eee; } }
73
+ border-bottom: 1px solid var(--mv-border-soft); } }
67
74
  /* Tool names are arbitrary identifiers; long ones must wrap inside the column
68
75
  rather than spill over the divider. */
69
76
  .tools button { display: block; width: 100%; text-align: left; padding: 6px 10px;
70
- border: 0; background: none; color: #1a1a1a; cursor: pointer; font: inherit;
71
- font-family: ui-monospace, monospace; font-size: 12px;
72
- overflow-wrap: anywhere; border-bottom: 1px solid #f4f4f4; }
73
- .tools button:hover { background: #f6f6f6; }
74
- .tools button[aria-current="true"] { background: #e8f0fe; font-weight: 600; }
77
+ border: 0; background: none; color: var(--mv-fg); cursor: pointer;
78
+ font: inherit; font-family: ui-monospace, monospace; font-size: 12px;
79
+ overflow-wrap: anywhere; border-bottom: 1px solid var(--mv-border-soft); }
80
+ .tools button:hover { background: var(--mv-hover); }
81
+ .tools button[aria-current="true"] { background: var(--mv-selected); font-weight: 600; }
75
82
 
76
83
  .form { flex: 1; padding: 10px; min-width: 0; }
77
- .desc { color: #666; margin: 0 0 8px; }
84
+ .desc { color: var(--mv-muted); margin: 0 0 8px; }
78
85
  label { display: block; margin-bottom: 7px; }
79
86
  .name { font-family: ui-monospace, monospace; font-size: 12px; }
80
- .req { color: #c5221f; }
81
- .hint { color: #888; font-size: 11px; }
87
+ .req { color: var(--mv-err-fg); }
88
+ .hint { color: var(--mv-faint); font-size: 11px; }
82
89
  /* color/background are required, not decorative: form controls do not inherit
83
90
  them, so without these the UA picks per-theme defaults and text can render
84
91
  white on white. */
85
92
  input, select, textarea { width: 100%; box-sizing: border-box; font: inherit; font-size: 12px;
86
- color: #1a1a1a; background: #fff; padding: 4px 6px;
87
- border: 1px solid #ddd; border-radius: 4px; }
93
+ color: var(--mv-fg); background: var(--mv-input-bg); padding: 4px 6px;
94
+ border: 1px solid var(--mv-control-border); border-radius: 4px; }
88
95
  input[type="checkbox"] { width: auto; }
89
96
  textarea { font-family: ui-monospace, monospace; }
90
- .run { margin-top: 4px; padding: 5px 14px; border: 1px solid #1565c0; border-radius: 4px;
91
- background: #1565c0; color: #fff; cursor: pointer; font: inherit; }
97
+ .run { margin-top: 4px; padding: 5px 14px; border: 1px solid var(--mv-accent);
98
+ border-radius: 4px; background: var(--mv-accent); color: var(--mv-accent-fg);
99
+ cursor: pointer; font: inherit; }
92
100
  .run:disabled { opacity: .6; cursor: default; }
93
- pre { margin: 8px 0 0; padding: 7px; color: #1a1a1a; background: #fafafa; border: 1px solid #eee;
94
- border-radius: 4px; font-size: 12px; white-space: pre-wrap; word-break: break-word;
95
- max-height: 180px; overflow: auto; }
96
- pre.error { background: #fce8e6; border-color: #f5c6c2; color: #c5221f; }
97
- .field-error { color: #c5221f; font-size: 11px; }
98
- .empty { padding: 20px; text-align: center; color: #888; }
101
+ pre { margin: 8px 0 0; padding: 7px; color: var(--mv-fg); background: var(--mv-surface);
102
+ border: 1px solid var(--mv-border-soft); border-radius: 4px; font-size: 12px;
103
+ white-space: pre-wrap; word-break: break-word; max-height: 180px; overflow: auto; }
104
+ pre.error { background: var(--mv-err-bg); border-color: var(--mv-err-border); color: var(--mv-err-fg); }
105
+ .field-error { color: var(--mv-err-fg); font-size: 11px; }
106
+ .empty { padding: 20px; text-align: center; color: var(--mv-faint); }
99
107
  `;
100
108
 
101
109
  export class MachviveWebmcpInspect extends HTMLElement {
@@ -104,7 +112,7 @@ export class MachviveWebmcpInspect extends HTMLElement {
104
112
  #onToolsChanged = () => this.#render();
105
113
 
106
114
  static get observedAttributes() {
107
- return ['floating', 'open'];
115
+ return ['floating', 'open', 'theme'];
108
116
  }
109
117
 
110
118
  constructor() {
@@ -126,6 +134,17 @@ export class MachviveWebmcpInspect extends HTMLElement {
126
134
  if (this.shadowRoot?.getElementById('root')) this.#render();
127
135
  }
128
136
 
137
+
138
+ /** Forces a palette regardless of the OS preference. null follows the OS. */
139
+ get theme() {
140
+ return this.getAttribute('theme');
141
+ }
142
+
143
+ set theme(value) {
144
+ if (value == null) this.removeAttribute('theme');
145
+ else this.setAttribute('theme', value);
146
+ }
147
+
129
148
  /** Opens the panel (floating mode only). */
130
149
  show() {
131
150
  this.setAttribute('open', '');
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Shared design tokens for the WebMCP UI components.
3
+ *
4
+ * Tokens are plain custom properties on :host, so a consumer can override any of
5
+ * them from outside the shadow DOM — custom properties inherit through shadow
6
+ * boundaries where ordinary styles do not.
7
+ *
8
+ * Resolution order, matching how a component should behave:
9
+ * 1. light palette on :host (the default)
10
+ * 2. dark palette when the OS asks for dark (unless theme="light" opts out)
11
+ * 3. dark palette when theme="dark" (explicit, wins over the OS)
12
+ */
13
+
14
+ const LIGHT = `
15
+ --mv-fg: #1a1a1a;
16
+ --mv-muted: #666;
17
+ --mv-faint: #6e6e6e;
18
+ --mv-bg: #fff;
19
+ --mv-surface: #fafafa;
20
+ --mv-input-bg: #fff;
21
+ --mv-border: #e2e2e2;
22
+ --mv-border-soft: #eee;
23
+ --mv-control-border: #ccc;
24
+ --mv-hover: #f2f2f2;
25
+ --mv-selected: #e8f0fe;
26
+ --mv-accent: #1565c0;
27
+ --mv-accent-fg: #fff;
28
+ --mv-ok-bg: #e6f4ea;
29
+ --mv-ok-fg: #0f6b2e;
30
+ --mv-err-bg: #fce8e6;
31
+ --mv-err-fg: #b3261e;
32
+ --mv-err-border: #f5c6c2;
33
+ --mv-danger: #a4161a;
34
+ --mv-danger-border: #d8a0a0;
35
+ --mv-shadow: rgba(0, 0, 0, .18);
36
+ `;
37
+
38
+ const DARK = `
39
+ --mv-fg: #e8eaed;
40
+ --mv-muted: #9aa0a6;
41
+ --mv-faint: #8b9096;
42
+ --mv-bg: #1f2125;
43
+ --mv-surface: #282b30;
44
+ --mv-input-bg: #16181b;
45
+ --mv-border: #3c4046;
46
+ --mv-border-soft: #33363b;
47
+ --mv-control-border: #4a4f56;
48
+ --mv-hover: #32363c;
49
+ --mv-selected: #1e3a5f;
50
+ --mv-accent: #5b9bf8;
51
+ --mv-accent-fg: #0b1220;
52
+ --mv-ok-bg: #16351f;
53
+ --mv-ok-fg: #7ee2a0;
54
+ --mv-err-bg: #3f1d1c;
55
+ --mv-err-fg: #ff9d97;
56
+ --mv-err-border: #5c2c2a;
57
+ --mv-danger: #ff9d97;
58
+ --mv-danger-border: #5c2c2a;
59
+ --mv-shadow: rgba(0, 0, 0, .5);
60
+ `;
61
+
62
+ /**
63
+ * CSS declaring both palettes. Prepend to a component's stylesheet.
64
+ *
65
+ * `color-scheme: light dark` lets UA-rendered parts (scrollbars, select popups,
66
+ * checkboxes, focus rings) follow the same theme as the tokens — without it the
67
+ * browser would paint light controls onto a dark panel.
68
+ */
69
+ export const THEME_CSS = `
70
+ :host {
71
+ color-scheme: light dark;
72
+ ${LIGHT} }
73
+
74
+ @media (prefers-color-scheme: dark) {
75
+ :host(:not([theme="light"])) {
76
+ ${DARK} }
77
+ }
78
+
79
+ /* Explicit choice beats the OS preference, in both directions. */
80
+ :host([theme="dark"]) {
81
+ ${DARK} }
82
+
83
+ :host([theme="light"]) {
84
+ color-scheme: light;
85
+ ${LIGHT} }
86
+ `;