@ashley-shrok/viewmodel-shell 0.3.14 → 0.4.1

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/dist/browser.js CHANGED
@@ -166,7 +166,7 @@ export class BrowserAdapter {
166
166
  }
167
167
  page(n, parent, on) {
168
168
  const el = document.createElement("div");
169
- el.className = "vms-page";
169
+ el.className = `vms-page${n.density === "compact" ? " vms-page--compact" : ""}${n.layout && n.layout !== "stack" ? ` vms-page--${n.layout}` : ""}`;
170
170
  if (n.title) {
171
171
  const h = document.createElement("h1");
172
172
  h.className = "vms-page__title";
@@ -178,7 +178,7 @@ export class BrowserAdapter {
178
178
  }
179
179
  section(n, parent, on) {
180
180
  const el = document.createElement("section");
181
- el.className = "vms-section";
181
+ el.className = `vms-section${n.variant === "card" ? " vms-section--card" : ""}${n.layout && n.layout !== "stack" ? ` vms-section--${n.layout}` : ""}`;
182
182
  if (n.heading) {
183
183
  const h = document.createElement("h2");
184
184
  h.className = "vms-section__heading";
@@ -206,7 +206,7 @@ export class BrowserAdapter {
206
206
  }
207
207
  form(n, parent, on) {
208
208
  const form = document.createElement("form");
209
- form.className = "vms-form";
209
+ form.className = `vms-form${n.layout && n.layout !== "stack" ? ` vms-form--${n.layout}` : ""}`;
210
210
  form.noValidate = true;
211
211
  this.kids(n.children, form, on);
212
212
  const submit = document.createElement("button");
package/dist/index.d.ts CHANGED
@@ -28,11 +28,19 @@ export type ViewNode = PageNode | SectionNode | ListNode | ListItemNode | FormNo
28
28
  export interface PageNode {
29
29
  type: "page";
30
30
  title?: string;
31
+ /** Density of global spacing. Omitted or "comfortable" = current behavior (no modifier class). "compact" emits .vms-page--compact. Closed union (D-03). */
32
+ density?: "comfortable" | "compact";
33
+ /** Layout preset arranging direct children. Omitted or "stack" = current vertical flow (no modifier class). "split" (equal 2-up), "cards" (uniform grid), "sidebar" (thin + wide app shell) emit .vms-page--{value}. Closed union (D-01/D-02; sidebar D-28). */
34
+ layout?: "stack" | "split" | "cards" | "sidebar";
31
35
  children: ViewNode[];
32
36
  }
33
37
  export interface SectionNode {
34
38
  type: "section";
35
39
  heading?: string;
40
+ /** Section surface variant. Omitted = current behavior (no modifier class). "card" emits .vms-section--card. Closed union (D-03). */
41
+ variant?: "card";
42
+ /** Layout preset arranging direct children. Omitted or "stack" = current vertical flow (no modifier class). "split" (equal 2-up), "cards" (uniform grid), "sidebar" (thin + wide app shell) emit .vms-section--{value}. Closed union (D-01/D-02; sidebar D-28). */
43
+ layout?: "stack" | "split" | "cards" | "sidebar";
36
44
  children: ViewNode[];
37
45
  }
38
46
  export interface ListNode {
@@ -51,6 +59,8 @@ export interface FormNode {
51
59
  type: "form";
52
60
  submitAction: ActionEvent;
53
61
  submitLabel?: string;
62
+ /** Layout preset for the form's controls. Omitted or "stack" = fields stacked (current, no modifier class). "inline" = field row + submit on one line (add/search bar) — emits .vms-form--inline. Closed union (D-29). */
63
+ layout?: "stack" | "inline";
54
64
  children: ViewNode[];
55
65
  }
56
66
  export interface FieldNode {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ashley-shrok/viewmodel-shell",
3
- "version": "0.3.14",
3
+ "version": "0.4.1",
4
4
  "description": "A server-driven UI framework where the wire format is structured enough that agents can build full-stack apps without ever opening a browser and all UI tests are pure unit tests with no browser runtime. Server returns a JSON tree of typed nodes; a thin TypeScript adapter renders it to DOM. Backend-agnostic — a .NET reference backend ships with the repo, but any language can produce the JSON contract.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,6 +27,10 @@
27
27
  "scripts": {
28
28
  "build": "tsc -p tsconfig.json",
29
29
  "check:core-globals": "node scripts/check-core-platform-globals.mjs",
30
+ "check:aa-contrast": "node scripts/check-aa-contrast.mjs",
31
+ "check:no-demo-style": "node scripts/check-no-demo-style.mjs",
32
+ "check:theme-byte-identity": "node scripts/check-theme-byte-identity.mjs",
33
+ "check:theme-function": "node scripts/check-theme-function.mjs",
30
34
  "test": "vitest run",
31
35
  "prepublishOnly": "npm run build"
32
36
  },
@@ -54,6 +58,7 @@
54
58
  "./themes/dark-rose.css": "./styles/themes/dark-rose.css",
55
59
  "./themes/dark-amber.css": "./styles/themes/dark-amber.css",
56
60
  "./themes/dark-teal.css": "./styles/themes/dark-teal.css",
61
+ "./themes/dark-purple.css": "./styles/themes/dark-purple.css",
57
62
  "./themes/light-purple.css": "./styles/themes/light-purple.css",
58
63
  "./themes/light-blue.css": "./styles/themes/light-blue.css",
59
64
  "./themes/light-green.css": "./styles/themes/light-green.css",
@@ -15,30 +15,49 @@
15
15
  ───────────────────────────────────────────────────────────────────────── */
16
16
 
17
17
  :root {
18
- --vms-bg: #0f0f11;
19
- --vms-surface: #18181c;
20
- --vms-surface-2: #222228;
21
- --vms-border: #2e2e38;
22
- --vms-accent: #7c6af7;
23
- --vms-accent-glow: rgba(124, 106, 247, 0.18);
24
- --vms-accent-dim: rgba(124, 106, 247, 0.35);
25
- --vms-text: #e8e8f0;
26
- --vms-text-muted: #6b6b80;
27
- --vms-done-bg: #3a3a48;
28
- --vms-done-text: #5a5a6e;
29
- --vms-error: #e05a5a;
30
- --vms-error-glow: rgba(224, 90, 90, 0.12);
31
- --vms-warning: #e0a823;
32
- --vms-priority-high: #e07015;
33
- --vms-success: #4dd17a;
34
- --vms-info: #4a9eff;
18
+ --vms-bg: #f7f7f9;
19
+ --vms-surface: #ffffff;
20
+ --vms-surface-2: #f0f0f4;
21
+ --vms-border: #d8d8e0;
22
+ --vms-accent: #5a4ad7;
23
+ --vms-accent-glow: rgba(90, 74, 215, 0.10);
24
+ --vms-accent-dim: rgba(90, 74, 215, 0.30);
25
+ --vms-text: #1a1a22;
26
+ --vms-text-muted: #6c6c80;
27
+ --vms-done-bg: #ececf2;
28
+ --vms-done-text: #9090a0;
29
+ --vms-error: #c2453d;
30
+ --vms-error-glow: rgba(194, 69, 61, 0.10);
31
+ --vms-warning: #a37510;
32
+ --vms-priority-high: #d76410;
33
+ --vms-success: #2da359;
34
+ --vms-info: #2277dd;
35
35
  --vms-radius: 10px;
36
36
  --vms-radius-sm: 6px;
37
37
  --vms-font-body: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
38
38
  --vms-font-head: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
39
39
  --vms-font-mono: ui-monospace, 'Cascadia Code', 'Fira Code', Menlo, Consolas, 'Liberation Mono', monospace;
40
40
  --vms-t: 0.15s ease;
41
- --vms-color-scheme: dark;
41
+ --vms-color-scheme: light;
42
+ /* Spacing scale — modular ~1.5, base --vms-space-xs 0.5rem (D-06/D-07) */
43
+ --vms-space-2xs: 0.25rem;
44
+ --vms-space-xs: 0.5rem;
45
+ --vms-space-sm: 0.75rem;
46
+ --vms-space-md: 1rem;
47
+ --vms-space-lg: 1.5rem;
48
+ --vms-space-xl: 2.25rem;
49
+ /* Type scale — all rem, modular ~1.2, base --vms-text-base 0.8125rem (D-06/D-09) */
50
+ --vms-text-xs: 0.6875rem;
51
+ --vms-text-sm: 0.75rem;
52
+ --vms-text-base: 0.8125rem;
53
+ --vms-text-md: 0.875rem;
54
+ --vms-text-lg: 1rem;
55
+ --vms-text-xl: 1.375rem;
56
+ --vms-text-2xl: 2.25rem;
57
+ /* Page shell (D-12) */
58
+ --vms-page-max: 1080px;
59
+ /* Card-grid min item width — additive override seam (D-05); host/theme-retunable */
60
+ --vms-card-min: 16rem;
42
61
  }
43
62
 
44
63
  /* ── Page chrome ──
@@ -71,40 +90,135 @@ body {
71
90
  }
72
91
 
73
92
  /* ── Page ── */
74
- .vms-page { display: flex; flex-direction: column; gap: 1.5rem; color: var(--vms-text); font-family: var(--vms-font-body); }
93
+ .vms-page {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: var(--vms-space-lg);
97
+ max-width: var(--vms-page-max);
98
+ margin-inline: auto;
99
+ padding-inline: clamp(1rem, 5vw, 2.25rem);
100
+ color: var(--vms-text);
101
+ font-family: var(--vms-font-body);
102
+ }
75
103
  .vms-page__title {
76
104
  font-family: var(--vms-font-head);
77
- font-size: 2.25rem;
105
+ font-size: var(--vms-text-2xl);
78
106
  font-weight: 600;
79
107
  letter-spacing: -0.02em;
80
- padding-bottom: 0.75rem;
108
+ padding-bottom: var(--vms-space-sm);
81
109
  border-bottom: 1px solid var(--vms-border);
82
110
  }
83
111
 
112
+ /* ── Density: compact (THEME-03, D-10) — remaps rhythm tokens only ── */
113
+ .vms-page--compact {
114
+ --vms-space-sm: 0.5rem;
115
+ --vms-space-md: 0.75rem;
116
+ --vms-space-lg: 1rem;
117
+ }
118
+
119
+ /* ── Layout preset: cards (LAYOUT-03, D-04) ──
120
+ auto-fit intrinsically collapses toward 1 column as the container narrows;
121
+ min(...,100%) floor prevents single-column overflow on a narrow viewport
122
+ (Pitfall 4). gap inherited from .vms-page/.vms-section — NOT redeclared. */
123
+ .vms-page--cards,
124
+ .vms-section--cards {
125
+ display: grid;
126
+ grid-template-columns: repeat(auto-fit, minmax(min(var(--vms-card-min), 100%), 1fr));
127
+ }
128
+ .vms-page--cards > .vms-page__title,
129
+ .vms-section--cards > .vms-section__heading {
130
+ grid-column: 1 / -1;
131
+ }
132
+
133
+ /* ── Layout preset: split (LAYOUT-02, D-06/D-07) ──
134
+ Capped exactly-2-then-1, equal-width (shared 1fr max), zero media queries.
135
+ Each track's MIN is >= max(16rem, 50% - gap): two ~50% tracks fill the row,
136
+ a 3rd would need >100% (impossible); when 50%-gap < 16rem on a narrow
137
+ container the 16rem floor wins and only 1 track fits.
138
+ gap inherited from .vms-page/.vms-section — NOT redeclared. */
139
+ .vms-page--split {
140
+ display: grid;
141
+ grid-template-columns:
142
+ repeat(auto-fit, minmax(max(16rem, calc(50% - var(--vms-space-lg))), 1fr));
143
+ }
144
+ .vms-section--split {
145
+ display: grid;
146
+ grid-template-columns:
147
+ repeat(auto-fit, minmax(max(16rem, calc(50% - var(--vms-space-sm))), 1fr));
148
+ }
149
+ .vms-page--split > .vms-page__title,
150
+ .vms-section--split > .vms-section__heading {
151
+ grid-column: 1 / -1;
152
+ }
153
+
154
+ /* ── Layout preset: sidebar (D-28) — thin + wide app shell ──
155
+ The common real-app shell: a narrow first column + a wide main that
156
+ grows to fill. Flex-wrap "holy grail": main grows aggressively so the
157
+ sidebar holds near its basis; when the row can't fit both they wrap to
158
+ stacked (zero media queries, like split/cards). Title/heading take their
159
+ own full-width row. First content child = sidebar; the rest = main.
160
+ gap inherited from .vms-page/.vms-section — NOT redeclared. */
161
+ .vms-page--sidebar,
162
+ .vms-section--sidebar {
163
+ display: flex;
164
+ flex-direction: row; /* override .vms-page/.vms-section base column */
165
+ flex-wrap: wrap;
166
+ align-items: flex-start;
167
+ }
168
+ .vms-page--sidebar > *,
169
+ .vms-section--sidebar > * { flex: 999 1 24rem; }
170
+ .vms-page--sidebar > :first-child:not(.vms-page__title),
171
+ .vms-page--sidebar > .vms-page__title + *,
172
+ .vms-section--sidebar > :first-child:not(.vms-section__heading),
173
+ .vms-section--sidebar > .vms-section__heading + * {
174
+ flex: 1 1 16rem;
175
+ max-width: 24rem;
176
+ }
177
+ .vms-page--sidebar > .vms-page__title,
178
+ .vms-section--sidebar > .vms-section__heading { flex: 0 0 100%; }
179
+
84
180
  /* ── Section ── */
85
- .vms-section { display: flex; flex-direction: column; gap: 0.75rem; }
181
+ .vms-section { display: flex; flex-direction: column; gap: var(--vms-space-sm); }
86
182
  .vms-section__heading {
87
- font-size: 11px;
183
+ font-size: var(--vms-text-xs);
88
184
  letter-spacing: 0.08em;
89
185
  text-transform: uppercase;
90
186
  color: var(--vms-text-muted);
91
187
  }
92
188
 
189
+ /* ── Section variant: card (THEME-04) — grouped surface, existing seam vars ── */
190
+ .vms-section--card {
191
+ background: var(--vms-surface);
192
+ border: 1px solid var(--vms-border);
193
+ border-radius: var(--vms-radius);
194
+ padding: var(--vms-space-md);
195
+ }
196
+
93
197
  /* ── Stat bar ── */
94
- .vms-stat-bar { display: flex; gap: 1.5rem; flex-wrap: wrap; }
95
- .vms-stat-bar__item { display: flex; align-items: baseline; gap: 0.4rem; }
96
- .vms-stat-bar__value { color: var(--vms-accent); font-weight: 600; font-size: 16px; }
198
+ .vms-stat-bar { display: flex; gap: var(--vms-space-lg); flex-wrap: wrap; }
199
+ .vms-stat-bar__item { display: flex; align-items: baseline; gap: var(--vms-space-xs); }
200
+ .vms-stat-bar__value { color: var(--vms-accent); font-weight: 600; font-size: var(--vms-text-lg); }
97
201
  .vms-stat-bar__label {
98
202
  color: var(--vms-text-muted);
99
- font-size: 11px;
203
+ font-size: var(--vms-text-xs);
100
204
  text-transform: uppercase;
101
205
  letter-spacing: 0.05em;
102
206
  }
103
207
 
104
208
  /* ── Form ── */
105
- .vms-form { display: flex; flex-direction: column; gap: 0.75rem; align-items: stretch; }
106
- .vms-field { display: flex; flex-direction: column; gap: 0.3rem; }
107
- .vms-field__label { font-size: 12px; color: var(--vms-text-muted); }
209
+ .vms-form { display: flex; flex-direction: column; gap: var(--vms-space-sm); align-items: stretch; }
210
+ /* Form layout preset: inline (D-29) field row + submit on one line (the
211
+ add/search-bar pattern). flex-direction:row over the column base; the
212
+ field grows, submit hugs; bottom-aligned so submit lines up with the
213
+ input (label sits above). Wraps on very narrow — zero media queries. */
214
+ .vms-form--inline {
215
+ flex-direction: row;
216
+ align-items: flex-end;
217
+ flex-wrap: wrap;
218
+ }
219
+ .vms-form--inline > .vms-field { flex: 1 1 12rem; }
220
+ .vms-field { display: flex; flex-direction: column; gap: var(--vms-space-2xs); }
221
+ .vms-field__label { font-size: var(--vms-text-sm); color: var(--vms-text-muted); }
108
222
  .vms-field__input {
109
223
  width: 100%;
110
224
  background: var(--vms-surface);
@@ -112,8 +226,8 @@ body {
112
226
  border-radius: var(--vms-radius);
113
227
  color: var(--vms-text);
114
228
  font-family: var(--vms-font-body);
115
- font-size: 14px;
116
- padding: 0.65rem 1rem;
229
+ font-size: var(--vms-text-md);
230
+ padding: var(--vms-space-sm) var(--vms-space-md);
117
231
  outline: none;
118
232
  transition: border-color var(--vms-t), box-shadow var(--vms-t);
119
233
  resize: vertical;
@@ -128,13 +242,13 @@ body {
128
242
  box-shadow: 0 0 0 3px var(--vms-accent-glow);
129
243
  }
130
244
  textarea.vms-field__input { min-height: 80px; }
131
- select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
245
+ select.vms-field__input { cursor: pointer; padding-right: var(--vms-space-xl); }
132
246
 
133
247
  /* Code editor field — monospaced, tab-aware, no spell-check chrome.
134
248
  Apps add syntax highlighting via .vms-field--code-{language} hooks. */
135
249
  .vms-field__input--code {
136
250
  font-family: var(--vms-font-mono);
137
- font-size: 13px;
251
+ font-size: var(--vms-text-base);
138
252
  line-height: 1.5;
139
253
  tab-size: 2;
140
254
  min-height: 140px;
@@ -143,7 +257,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
143
257
  }
144
258
 
145
259
  /* Form-collected checkbox (FieldNode inputType="checkbox") — inline label. */
146
- .vms-field--checkbox { flex-direction: row; align-items: center; gap: 0.5rem; }
260
+ .vms-field--checkbox { flex-direction: row; align-items: center; gap: var(--vms-space-xs); }
147
261
  .vms-field--checkbox .vms-field__input {
148
262
  width: 18px;
149
263
  height: 18px;
@@ -178,7 +292,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
178
292
  border-left: none;
179
293
  transform: rotate(40deg);
180
294
  }
181
- .vms-checkbox__label { margin-left: 0.5rem; font-size: 13px; }
295
+ .vms-checkbox__label { margin-left: var(--vms-space-xs); font-size: var(--vms-text-base); }
182
296
 
183
297
  /* ── Buttons ── */
184
298
  .vms-button {
@@ -188,8 +302,8 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
188
302
  color: var(--vms-text);
189
303
  cursor: pointer;
190
304
  font-family: var(--vms-font-body);
191
- font-size: 13px;
192
- padding: 0.55rem 0.9rem;
305
+ font-size: var(--vms-text-base);
306
+ padding: var(--vms-space-sm) var(--vms-space-md);
193
307
  transition: background var(--vms-t), border-color var(--vms-t), transform var(--vms-t);
194
308
  }
195
309
  .vms-button:hover { border-color: var(--vms-accent-dim); }
@@ -198,7 +312,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
198
312
  background: var(--vms-accent);
199
313
  border-color: var(--vms-accent);
200
314
  color: #fff;
201
- padding: 0.65rem 1.25rem;
315
+ padding: var(--vms-space-sm) var(--vms-space-lg);
202
316
  align-self: flex-start;
203
317
  }
204
318
  .vms-button--primary:hover { filter: brightness(1.1); }
@@ -206,8 +320,8 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
206
320
  background: transparent;
207
321
  border-color: var(--vms-accent-dim);
208
322
  color: var(--vms-accent);
209
- font-size: 12px;
210
- padding: 0.35rem 0.7rem;
323
+ font-size: var(--vms-text-sm);
324
+ padding: var(--vms-space-2xs) var(--vms-space-sm);
211
325
  }
212
326
  .vms-button--secondary:hover { border-color: var(--vms-accent); background: var(--vms-accent-glow); }
213
327
  .vms-button--danger {
@@ -223,7 +337,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
223
337
  border: none;
224
338
  color: var(--vms-text-muted);
225
339
  opacity: 0;
226
- padding: 0.25rem 0.4rem;
340
+ padding: var(--vms-space-2xs) var(--vms-space-xs);
227
341
  border-radius: var(--vms-radius-sm);
228
342
  transition: opacity var(--vms-t), color var(--vms-t), background var(--vms-t);
229
343
  }
@@ -233,11 +347,11 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
233
347
  /* ── Tabs ── */
234
348
  .vms-tabs {
235
349
  display: flex;
236
- gap: 0.25rem;
350
+ gap: var(--vms-space-2xs);
237
351
  background: var(--vms-surface);
238
352
  border: 1px solid var(--vms-border);
239
353
  border-radius: var(--vms-radius);
240
- padding: 0.25rem;
354
+ padding: var(--vms-space-2xs);
241
355
  }
242
356
  .vms-tabs__tab {
243
357
  flex: 1;
@@ -247,9 +361,9 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
247
361
  color: var(--vms-text-muted);
248
362
  cursor: pointer;
249
363
  font-family: var(--vms-font-body);
250
- font-size: 12px;
364
+ font-size: var(--vms-text-sm);
251
365
  letter-spacing: 0.05em;
252
- padding: 0.4rem 0;
366
+ padding: var(--vms-space-xs) 0;
253
367
  text-transform: uppercase;
254
368
  transition: background var(--vms-t), color var(--vms-t);
255
369
  }
@@ -257,15 +371,15 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
257
371
  .vms-tabs__tab--active { background: var(--vms-surface-2); color: var(--vms-accent); }
258
372
 
259
373
  /* ── List ── */
260
- .vms-list { list-style: none; display: flex; flex-direction: column; gap: 0.375rem; padding: 0; margin: 0; }
374
+ .vms-list { list-style: none; display: flex; flex-direction: column; gap: var(--vms-space-xs); padding: 0; margin: 0; }
261
375
  .vms-list-item {
262
376
  display: flex;
263
377
  align-items: center;
264
- gap: 0.75rem;
378
+ gap: var(--vms-space-sm);
265
379
  background: var(--vms-surface);
266
380
  border: 1px solid var(--vms-border);
267
381
  border-radius: var(--vms-radius);
268
- padding: 0.75rem 1rem;
382
+ padding: var(--vms-space-sm) var(--vms-space-md);
269
383
  transition: border-color var(--vms-t), background var(--vms-t);
270
384
  }
271
385
  .vms-list-item:hover { border-color: var(--vms-accent-dim); }
@@ -285,6 +399,16 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
285
399
  .vms-list-item--success:hover { border-left-color: var(--vms-success); }
286
400
  .vms-list-item--info:hover { border-left-color: var(--vms-info); }
287
401
 
402
+ /* Selection state for master-detail / list-detail patterns (D-27). The
403
+ currently-selected row. Stronger than the semantic hints (selection is
404
+ the primary state) and stable through hover. Themable: uses only seam
405
+ tokens, so it works across every shipped theme. */
406
+ .vms-list-item--active,
407
+ .vms-list-item--active:hover {
408
+ border-color: var(--vms-accent);
409
+ background: var(--vms-accent-glow);
410
+ }
411
+
288
412
  @keyframes vms-in {
289
413
  from { opacity: 0; transform: translateY(4px); }
290
414
  to { opacity: 1; transform: translateY(0); }
@@ -292,12 +416,12 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
292
416
 
293
417
  /* ── Text ── */
294
418
  .vms-text { flex: 1; line-height: 1.4; word-break: break-word; }
295
- .vms-text--heading { font-family: var(--vms-font-head); font-size: 1.5rem; }
296
- .vms-text--subheading { font-size: 1.05rem; font-weight: 600; }
419
+ .vms-text--heading { font-family: var(--vms-font-head); font-size: var(--vms-text-xl); }
420
+ .vms-text--subheading { font-size: var(--vms-text-lg); font-weight: 600; }
297
421
  .vms-text--body { line-height: 1.6; }
298
- .vms-text--muted { color: var(--vms-text-muted); font-size: 12px; }
422
+ .vms-text--muted { color: var(--vms-text-muted); font-size: var(--vms-text-sm); }
299
423
  .vms-text--strikethrough { color: var(--vms-done-text); text-decoration: line-through; }
300
- .vms-text--error { color: var(--vms-error); font-size: 13px; }
424
+ .vms-text--error { color: var(--vms-error); font-size: var(--vms-text-base); }
301
425
  .vms-text--pre { font-family: var(--vms-font-mono); white-space: pre; }
302
426
 
303
427
  /* ── Link ── */
@@ -325,7 +449,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
325
449
  display: flex;
326
450
  align-items: center;
327
451
  justify-content: center;
328
- padding: 1rem;
452
+ padding: var(--vms-space-md);
329
453
  z-index: 1000;
330
454
  animation: vms-in 0.15s ease;
331
455
  }
@@ -349,25 +473,25 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
349
473
  display: flex;
350
474
  align-items: center;
351
475
  justify-content: space-between;
352
- padding: 1rem 1.25rem;
476
+ padding: var(--vms-space-md) var(--vms-space-lg);
353
477
  border-bottom: 1px solid var(--vms-border);
354
478
  }
355
- .vms-modal__title { font-family: var(--vms-font-head); font-size: 1.25rem; }
479
+ .vms-modal__title { font-family: var(--vms-font-head); font-size: var(--vms-text-xl); }
356
480
  .vms-modal__close {
357
481
  background: transparent;
358
482
  border: none;
359
483
  color: var(--vms-text-muted);
360
484
  cursor: pointer;
361
- font-size: 1.1rem;
362
- padding: 0.25rem 0.5rem;
485
+ font-size: var(--vms-text-xl);
486
+ padding: var(--vms-space-2xs) var(--vms-space-xs);
363
487
  }
364
488
  .vms-modal__close:hover { color: var(--vms-text); }
365
489
  .vms-modal__body {
366
- padding: 1.25rem;
490
+ padding: var(--vms-space-lg);
367
491
  overflow-y: auto;
368
492
  display: flex;
369
493
  flex-direction: column;
370
- gap: 0.75rem;
494
+ gap: var(--vms-space-sm);
371
495
  }
372
496
  /* Children of the modal body must not be flex-shrunk below their natural
373
497
  height — otherwise a table-wrapper (overflow:hidden) inside the modal
@@ -377,13 +501,13 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
377
501
  .vms-modal__footer {
378
502
  display: flex;
379
503
  justify-content: flex-end;
380
- gap: 0.5rem;
381
- padding: 0.75rem 1.25rem;
504
+ gap: var(--vms-space-xs);
505
+ padding: var(--vms-space-sm) var(--vms-space-lg);
382
506
  border-top: 1px solid var(--vms-border);
383
507
  }
384
508
  /* Buttons inside the footer ignore the framework's default align-self so
385
509
  they sit naturally on the footer row. */
386
- .vms-modal__footer .vms-button--primary { align-self: auto; padding: 0.55rem 1rem; }
510
+ .vms-modal__footer .vms-button--primary { align-self: auto; padding: var(--vms-space-sm) var(--vms-space-md); }
387
511
 
388
512
  /* ── Table ── */
389
513
  .vms-table-wrapper {
@@ -393,15 +517,15 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
393
517
  7-column table inside a medium modal). Always-reachable beats clipped. */
394
518
  overflow-x: auto;
395
519
  }
396
- .vms-table { width: 100%; border-collapse: collapse; font-size: 13px; }
520
+ .vms-table { width: 100%; border-collapse: collapse; font-size: var(--vms-text-base); }
397
521
  .vms-table__th {
398
522
  background: var(--vms-surface-2);
399
523
  color: var(--vms-text-muted);
400
524
  font-weight: 600;
401
- font-size: 11px;
525
+ font-size: var(--vms-text-xs);
402
526
  text-transform: uppercase;
403
527
  letter-spacing: 0.05em;
404
- padding: 0.6rem 0.9rem;
528
+ padding: var(--vms-space-sm) var(--vms-space-md);
405
529
  text-align: left;
406
530
  border-bottom: 1px solid var(--vms-border);
407
531
  }
@@ -409,7 +533,7 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
409
533
  .vms-table__th--sortable:hover { color: var(--vms-text); }
410
534
  .vms-table__th--asc::after { content: ' ▲'; color: var(--vms-accent); }
411
535
  .vms-table__th--desc::after { content: ' ▼'; color: var(--vms-accent); }
412
- .vms-table__filter-row th { padding: 0.4rem 0.5rem; background: var(--vms-surface); border-bottom: 1px solid var(--vms-border); }
536
+ .vms-table__filter-row th { padding: var(--vms-space-xs) var(--vms-space-xs); background: var(--vms-surface); border-bottom: 1px solid var(--vms-border); }
413
537
  .vms-table__filter-input {
414
538
  width: 100%;
415
539
  background: var(--vms-surface-2);
@@ -417,8 +541,8 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
417
541
  border-radius: var(--vms-radius-sm);
418
542
  color: var(--vms-text);
419
543
  font-family: var(--vms-font-body);
420
- font-size: 12px;
421
- padding: 0.3rem 0.5rem;
544
+ font-size: var(--vms-text-sm);
545
+ padding: var(--vms-space-2xs) var(--vms-space-xs);
422
546
  outline: none;
423
547
  }
424
548
  .vms-table__filter-input:focus { border-color: var(--vms-accent); }
@@ -426,10 +550,18 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
426
550
  .vms-table__row:not(:last-child) .vms-table__td { border-bottom: 1px solid var(--vms-border); }
427
551
  .vms-table__row--clickable { cursor: pointer; }
428
552
  .vms-table__row--clickable:hover { background: var(--vms-surface); }
553
+ /* Row status variants — tints derive from theme vars (color-mix), so a
554
+ custom :root theme recolors them automatically; no app CSS, no literals. */
429
555
  .vms-table__row--done { opacity: 0.6; }
430
- .vms-table__row--warning { background: rgba(212, 130, 26, 0.06); }
431
- .vms-table__row--critical { background: rgba(224, 90, 90, 0.06); }
432
- .vms-table__td { padding: 0.6rem 0.9rem; vertical-align: middle; }
556
+ .vms-table__row--disabled { opacity: 0.55; color: var(--vms-text-muted); }
557
+ .vms-table__row--disabled.vms-table__row--clickable { cursor: default; }
558
+ .vms-table__row--disabled.vms-table__row--clickable:hover { background: transparent; }
559
+ .vms-table__row--success { background: color-mix(in srgb, var(--vms-success) 8%, transparent); }
560
+ .vms-table__row--running { background: color-mix(in srgb, var(--vms-accent) 8%, transparent); }
561
+ .vms-table__row--warning { background: color-mix(in srgb, var(--vms-warning) 9%, transparent); }
562
+ .vms-table__row--critical,
563
+ .vms-table__row--danger { background: color-mix(in srgb, var(--vms-error) 9%, transparent); }
564
+ .vms-table__td { padding: var(--vms-space-sm) var(--vms-space-md); vertical-align: middle; }
433
565
  .vms-table__link { color: var(--vms-accent); text-decoration: none; }
434
566
  .vms-table__link:hover { text-decoration: underline; }
435
567
 
@@ -445,14 +577,14 @@ select.vms-field__input { cursor: pointer; padding-right: 2.25rem; }
445
577
  display: flex;
446
578
  align-items: center;
447
579
  justify-content: space-between;
448
- font-size: 13px;
449
- margin-bottom: 1rem;
450
- padding: 0.65rem 1rem;
580
+ font-size: var(--vms-text-base);
581
+ margin-bottom: var(--vms-space-md);
582
+ padding: var(--vms-space-sm) var(--vms-space-md);
451
583
  }
452
584
  .vms-error button {
453
585
  background: none;
454
586
  border: none;
455
587
  color: inherit;
456
588
  cursor: pointer;
457
- font-size: 16px;
589
+ font-size: var(--vms-text-lg);
458
590
  }
@@ -1,6 +1,27 @@
1
- /* Dark theme · amber accent. Override on top of styles.css. */
1
+ /* Dark theme · amber accent. Self-sufficient full override on top of styles.css.
2
+ (Pre-0.4.0 this was an accent-only overlay that inherited the then-dark
3
+ default; the D-01 light re-base removed that base, so it now carries the
4
+ full dark color set itself — corrected, see CHANGELOG/MIGRATION.)
5
+ import "@ashley-shrok/viewmodel-shell/styles.css";
6
+ import "@ashley-shrok/viewmodel-shell/themes/dark-amber.css";
7
+ */
2
8
  :root {
3
- --vms-accent: #f0a830;
4
- --vms-accent-glow: rgba(240, 168, 48, 0.18);
5
- --vms-accent-dim: rgba(240, 168, 48, 0.35);
9
+ --vms-bg: #0f0f11;
10
+ --vms-surface: #18181c;
11
+ --vms-surface-2: #222228;
12
+ --vms-border: #2e2e38;
13
+ --vms-accent: #f0a830;
14
+ --vms-accent-glow: rgba(240, 168, 48, 0.18);
15
+ --vms-accent-dim: rgba(240, 168, 48, 0.35);
16
+ --vms-text: #e8e8f0;
17
+ --vms-text-muted: #9090a8;
18
+ --vms-done-bg: #3a3a48;
19
+ --vms-done-text: #5a5a6e;
20
+ --vms-error: #e05a5a;
21
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
22
+ --vms-warning: #e0a823;
23
+ --vms-priority-high: #e07015;
24
+ --vms-success: #4dd17a;
25
+ --vms-info: #4a9eff;
26
+ --vms-color-scheme: dark;
6
27
  }
@@ -1,6 +1,27 @@
1
- /* Dark theme · blue accent. Override on top of styles.css. */
1
+ /* Dark theme · blue accent. Self-sufficient full override on top of styles.css.
2
+ (Pre-0.4.0 this was an accent-only overlay that inherited the then-dark
3
+ default; the D-01 light re-base removed that base, so it now carries the
4
+ full dark color set itself — corrected, see CHANGELOG/MIGRATION.)
5
+ import "@ashley-shrok/viewmodel-shell/styles.css";
6
+ import "@ashley-shrok/viewmodel-shell/themes/dark-blue.css";
7
+ */
2
8
  :root {
3
- --vms-accent: #4a9eff;
4
- --vms-accent-glow: rgba(74, 158, 255, 0.18);
5
- --vms-accent-dim: rgba(74, 158, 255, 0.35);
9
+ --vms-bg: #0f0f11;
10
+ --vms-surface: #18181c;
11
+ --vms-surface-2: #222228;
12
+ --vms-border: #2e2e38;
13
+ --vms-accent: #4a9eff;
14
+ --vms-accent-glow: rgba(74, 158, 255, 0.18);
15
+ --vms-accent-dim: rgba(74, 158, 255, 0.35);
16
+ --vms-text: #e8e8f0;
17
+ --vms-text-muted: #9090a8;
18
+ --vms-done-bg: #3a3a48;
19
+ --vms-done-text: #5a5a6e;
20
+ --vms-error: #e05a5a;
21
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
22
+ --vms-warning: #e0a823;
23
+ --vms-priority-high: #e07015;
24
+ --vms-success: #4dd17a;
25
+ --vms-info: #4a9eff;
26
+ --vms-color-scheme: dark;
6
27
  }
@@ -1,6 +1,27 @@
1
- /* Dark theme · green accent. Override on top of styles.css. */
1
+ /* Dark theme · green accent. Self-sufficient full override on top of styles.css.
2
+ (Pre-0.4.0 this was an accent-only overlay that inherited the then-dark
3
+ default; the D-01 light re-base removed that base, so it now carries the
4
+ full dark color set itself — corrected, see CHANGELOG/MIGRATION.)
5
+ import "@ashley-shrok/viewmodel-shell/styles.css";
6
+ import "@ashley-shrok/viewmodel-shell/themes/dark-green.css";
7
+ */
2
8
  :root {
3
- --vms-accent: #4dd17a;
4
- --vms-accent-glow: rgba(77, 209, 122, 0.18);
5
- --vms-accent-dim: rgba(77, 209, 122, 0.35);
9
+ --vms-bg: #0f0f11;
10
+ --vms-surface: #18181c;
11
+ --vms-surface-2: #222228;
12
+ --vms-border: #2e2e38;
13
+ --vms-accent: #4dd17a;
14
+ --vms-accent-glow: rgba(77, 209, 122, 0.18);
15
+ --vms-accent-dim: rgba(77, 209, 122, 0.35);
16
+ --vms-text: #e8e8f0;
17
+ --vms-text-muted: #9090a8;
18
+ --vms-done-bg: #3a3a48;
19
+ --vms-done-text: #5a5a6e;
20
+ --vms-error: #e05a5a;
21
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
22
+ --vms-warning: #e0a823;
23
+ --vms-priority-high: #e07015;
24
+ --vms-success: #4dd17a;
25
+ --vms-info: #4a9eff;
26
+ --vms-color-scheme: dark;
6
27
  }
@@ -0,0 +1,24 @@
1
+ /* Dark-purple theme — the pre-0.4.0 shipped default, preserved verbatim.
2
+ import "@ashley-shrok/viewmodel-shell/styles.css";
3
+ import "@ashley-shrok/viewmodel-shell/themes/dark-purple.css";
4
+ */
5
+ :root {
6
+ --vms-bg: #0f0f11;
7
+ --vms-surface: #18181c;
8
+ --vms-surface-2: #222228;
9
+ --vms-border: #2e2e38;
10
+ --vms-accent: #7c6af7;
11
+ --vms-accent-glow: rgba(124, 106, 247, 0.18);
12
+ --vms-accent-dim: rgba(124, 106, 247, 0.35);
13
+ --vms-text: #e8e8f0;
14
+ --vms-text-muted: #9090a8;
15
+ --vms-done-bg: #3a3a48;
16
+ --vms-done-text: #5a5a6e;
17
+ --vms-error: #e05a5a;
18
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
19
+ --vms-warning: #e0a823;
20
+ --vms-priority-high: #e07015;
21
+ --vms-success: #4dd17a;
22
+ --vms-info: #4a9eff;
23
+ --vms-color-scheme: dark;
24
+ }
@@ -1,6 +1,27 @@
1
- /* Dark theme · rose accent. Override on top of styles.css. */
1
+ /* Dark theme · rose accent. Self-sufficient full override on top of styles.css.
2
+ (Pre-0.4.0 this was an accent-only overlay that inherited the then-dark
3
+ default; the D-01 light re-base removed that base, so it now carries the
4
+ full dark color set itself — corrected, see CHANGELOG/MIGRATION.)
5
+ import "@ashley-shrok/viewmodel-shell/styles.css";
6
+ import "@ashley-shrok/viewmodel-shell/themes/dark-rose.css";
7
+ */
2
8
  :root {
3
- --vms-accent: #ed5b8e;
4
- --vms-accent-glow: rgba(237, 91, 142, 0.18);
5
- --vms-accent-dim: rgba(237, 91, 142, 0.35);
9
+ --vms-bg: #0f0f11;
10
+ --vms-surface: #18181c;
11
+ --vms-surface-2: #222228;
12
+ --vms-border: #2e2e38;
13
+ --vms-accent: #ed5b8e;
14
+ --vms-accent-glow: rgba(237, 91, 142, 0.18);
15
+ --vms-accent-dim: rgba(237, 91, 142, 0.35);
16
+ --vms-text: #e8e8f0;
17
+ --vms-text-muted: #9090a8;
18
+ --vms-done-bg: #3a3a48;
19
+ --vms-done-text: #5a5a6e;
20
+ --vms-error: #e05a5a;
21
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
22
+ --vms-warning: #e0a823;
23
+ --vms-priority-high: #e07015;
24
+ --vms-success: #4dd17a;
25
+ --vms-info: #4a9eff;
26
+ --vms-color-scheme: dark;
6
27
  }
@@ -1,6 +1,27 @@
1
- /* Dark theme · teal accent. Override on top of styles.css. */
1
+ /* Dark theme · teal accent. Self-sufficient full override on top of styles.css.
2
+ (Pre-0.4.0 this was an accent-only overlay that inherited the then-dark
3
+ default; the D-01 light re-base removed that base, so it now carries the
4
+ full dark color set itself — corrected, see CHANGELOG/MIGRATION.)
5
+ import "@ashley-shrok/viewmodel-shell/styles.css";
6
+ import "@ashley-shrok/viewmodel-shell/themes/dark-teal.css";
7
+ */
2
8
  :root {
3
- --vms-accent: #4ed1d1;
4
- --vms-accent-glow: rgba(78, 209, 209, 0.18);
5
- --vms-accent-dim: rgba(78, 209, 209, 0.35);
9
+ --vms-bg: #0f0f11;
10
+ --vms-surface: #18181c;
11
+ --vms-surface-2: #222228;
12
+ --vms-border: #2e2e38;
13
+ --vms-accent: #4ed1d1;
14
+ --vms-accent-glow: rgba(78, 209, 209, 0.18);
15
+ --vms-accent-dim: rgba(78, 209, 209, 0.35);
16
+ --vms-text: #e8e8f0;
17
+ --vms-text-muted: #9090a8;
18
+ --vms-done-bg: #3a3a48;
19
+ --vms-done-text: #5a5a6e;
20
+ --vms-error: #e05a5a;
21
+ --vms-error-glow: rgba(224, 90, 90, 0.12);
22
+ --vms-warning: #e0a823;
23
+ --vms-priority-high: #e07015;
24
+ --vms-success: #4dd17a;
25
+ --vms-info: #4a9eff;
26
+ --vms-color-scheme: dark;
6
27
  }