@vttforge/styles 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,70 @@
1
+ # @vttforge/styles
2
+
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 9462144: Require Node 26.
8
+
9
+ The floor moves from `>=22.14.0` to `>=26.0.0` across every package and the
10
+ four scaffolding templates, and the bundler target for the Node-side
11
+ packages moves from `node22` to `node26`.
12
+
13
+ Node 22 entered maintenance in October 2025 and receives security fixes
14
+ only. Node 26 becomes the active LTS line on 2026-10-28.
15
+
16
+ This is breaking for anyone on Node 22 or 24. It is marked `minor` rather
17
+ than `major` on purpose: these packages are still on 0.x, where a minor
18
+ signals the break, and a major would push every package to 1.0.0 — a claim
19
+ of API stability that has not been audited, on packages two of which are
20
+ still stubs.
21
+
22
+ The templates move to the versions this release publishes. On 0.x a caret
23
+ pins the minor, so their old ranges would not have matched.
24
+
25
+ CI now pins Node through `actions/setup-node` instead of inheriting whatever
26
+ the runner image ships, so the version the packages declare is the version
27
+ they are tested on. It was not before: the workflow took the image's Node,
28
+ and nothing enforced the declared floor because `engine-strict` is not set.
29
+
30
+ ## 0.2.0
31
+
32
+ ### Minor Changes
33
+
34
+ - 45b9547: Generate `dist/tokens.css` from W3C DTCG `tokens.json` via Style Dictionary 4. Ships the Forge token set (warm-dark + ember accent) as the default, with light overrides at `[data-theme="light"]` and an opt-in Foundry Theme V2 mapping at `[data-theme="foundry"]`.
35
+
36
+ `@vttforge/styles/tokens.css` now resolves to the generated output. The previous placeholder `tokens.css` (Foundry-only fallback) is removed.
37
+
38
+ - 6bde407: Ship the full primitive set adapted from the Design System spec:
39
+ - `reset.css`: scoped reset for `.vttf-app`, `.vttf-sheet`, `form[data-vttf]` (does not override Foundry's `reset` layer).
40
+ - `base.css`: element baselines, focus-ring rendering, `.vttf-sr-only` utility.
41
+ - `components.css`: buttons (primary/secondary/ghost/danger), badges (7 hues), code block with syntax tokens (`.vttf-tk-*`), inputs/textarea/select with ARIA invalid state, checkbox/radio/switch/range/segmented control, tabs with ARIA selected, sheet primitives (`.vttf-sheet`, `.vttf-sheet__head`, `.vttf-sheet__body`, `.vttf-portrait`, `.vttf-stat-pill`, `.vttf-dropzone`, `.vttf-item-row`).
42
+ - `themes/forge.css`: adds `@media (prefers-color-scheme: light) [data-theme="auto"]` so `data-theme="auto"` follows the GM's OS preference. Explicit `data-theme="light"` and `data-theme="foundry"` continue to be handled by `tokens.css`.
43
+ - `preview/index.html`: Storybook-lite page that renders every primitive with a theme toggle (dark → light → foundry → auto).
44
+
45
+ Translucent tints use `color-mix(in oklch, var(--vttf-ember) 8%, transparent)` (HANDOFF §5) so re-theming the ember token propagates automatically. States use ARIA attributes first (`aria-selected`, `aria-invalid`, `aria-pressed`) with `.is-*` classes as fallback. `:focus-visible` is owned by the base layer.
46
+
47
+ Adds the `vttforge.themes` cascade sub-layer and bumps the layer order in both entry points.
48
+
49
+ ## 0.1.0
50
+
51
+ ### Minor Changes
52
+
53
+ - 4900e83: Foundation MVP (PR 4 of 4) — `@vttforge/core` ships its first runtime surface (registerSystem, SystemConfig, BaseTypeDataModel, BaseActorSheet, VttfError + VTTF-NNNN registry) and `@vttforge/styles` ships its first `--vttf-*` token set wrapped in the `vttforge.tokens` cascade layer.
54
+
55
+ Both packages have working consumer entrypoints (verified by an external smoke test loading the built `.mjs` from a throwaway dir) and the SDK contracts match the canonical Foundry v13 patterns (TypeDataModel migration, ActorSheetV2 + HandlebarsApplicationMixin, staged init hooks, marker classes).
56
+
57
+ Status remains pre-1.0 and APIs are explicitly unstable — these are the first releases that have real code instead of placeholder `export {}`.
58
+
59
+ ## 0.0.1
60
+
61
+ Initial functional release (v0.1 MVP slice).
62
+
63
+ ### Added
64
+
65
+ - `tokens.css` — `--vttf-*` design tokens that fall back to Foundry v13's own `--color-*` / `--font-*` custom properties when present, with literal defaults for non-Foundry environments. Covers text, surfaces, accents, spacing (4-pt grid), radii, and typography.
66
+ - Layer order preserved: tokens live under `vttforge.tokens`, leaving `vttforge.reset/.base/.components` empty until v0.1.0.
67
+
68
+ ### Pending
69
+
70
+ - Real Foundry Theme V2 (`CONST.CSS_THEMES`) binding once we wire that surface in.
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @vttforge/styles
2
+
3
+ CSS-only package: design tokens, scoped reset, base styles, sheet primitives, and opt-in themes. Built on [CSS Cascade Layers](https://developer.mozilla.org/en-US/docs/Web/CSS/@layer) for predictable composition with Foundry v13's own layer order.
4
+
5
+ > **Status:** v0.0.1 placeholder. Token contract finalizes in v0.1.0 once Foundry v13+ `CONST.CSS_THEMES` is wired in.
6
+
7
+ ## Usage
8
+
9
+ Default — one import, sub-layered:
10
+
11
+ ```css
12
+ @import '@vttforge/styles';
13
+ ```
14
+
15
+ Mantine-style — wrap everything in a single `@layer vttforge`:
16
+
17
+ ```css
18
+ @import '@vttforge/styles/styles.layer.css';
19
+ ```
20
+
21
+ Cherry-pick:
22
+
23
+ ```css
24
+ @import '@vttforge/styles/tokens.css';
25
+ @import '@vttforge/styles/components.css';
26
+ ```
27
+
28
+ ## Layer names
29
+
30
+ VTTForge uses a vendored prefix: `vttforge.tokens`, `vttforge.reset`, `vttforge.base`, `vttforge.components`. Foundry v13 owns the top-level layer names (`reset, variables, elements, blocks, applications, …, system, modules`) and auto-wraps system manifest CSS in its `system` layer.
package/base.css ADDED
@@ -0,0 +1,69 @@
1
+ /*!
2
+ * @vttforge/styles — base.css
3
+ *
4
+ * Element baselines and shared utilities on top of `tokens` + `reset`.
5
+ * Owns `--vttf-focus-ring` rendering; component layers MUST NOT override `:focus-visible`.
6
+ */
7
+
8
+ @layer vttforge.base {
9
+ .vttf-app,
10
+ .vttf-sheet {
11
+ font-family: var(--vttf-font-body);
12
+ font-size: var(--vttf-text-size-body);
13
+ line-height: 1.5;
14
+ color: var(--vttf-text);
15
+ background: var(--vttf-bg);
16
+ }
17
+
18
+ .vttf-app :focus-visible,
19
+ .vttf-sheet :focus-visible,
20
+ form[data-vttf] :focus-visible {
21
+ outline: none;
22
+ box-shadow: var(--vttf-focus-ring);
23
+ }
24
+
25
+ .vttf-app a,
26
+ .vttf-sheet a {
27
+ color: var(--vttf-ember);
28
+ text-decoration: none;
29
+ transition: color var(--vttf-duration-fast) var(--vttf-ease-out);
30
+ }
31
+
32
+ .vttf-app a:hover,
33
+ .vttf-sheet a:hover {
34
+ color: var(--vttf-ember-glow);
35
+ text-decoration: underline;
36
+ }
37
+
38
+ .vttf-app h1,
39
+ .vttf-app h2,
40
+ .vttf-app h3,
41
+ .vttf-sheet h1,
42
+ .vttf-sheet h2,
43
+ .vttf-sheet h3 {
44
+ font-family: var(--vttf-font-display);
45
+ font-weight: var(--vttf-text-weight-semibold);
46
+ letter-spacing: -0.02em;
47
+ margin: 0;
48
+ }
49
+
50
+ .vttf-app code,
51
+ .vttf-app pre,
52
+ .vttf-sheet code,
53
+ .vttf-sheet pre {
54
+ font-family: var(--vttf-font-mono);
55
+ }
56
+
57
+ /* Utility: visually hidden but accessible to screen readers */
58
+ .vttf-sr-only {
59
+ position: absolute;
60
+ width: 1px;
61
+ height: 1px;
62
+ padding: 0;
63
+ margin: -1px;
64
+ overflow: hidden;
65
+ clip: rect(0, 0, 0, 0);
66
+ white-space: nowrap;
67
+ border: 0;
68
+ }
69
+ }
package/components.css ADDED
@@ -0,0 +1,746 @@
1
+ /*!
2
+ * @vttforge/styles — components.css
3
+ *
4
+ * Sheet primitives consumed via .vttf-* classes. Tokens-only — every color,
5
+ * radius, shadow, and timing comes from `--vttf-*` (see tokens.css). Translucent
6
+ * tints use color-mix() against ember/rose tokens so re-themes propagate.
7
+ *
8
+ * BEM-light: .vttf-name (root), .vttf-name__part (parts), .vttf-name--variant
9
+ * (modifiers). ARIA states preferred (aria-selected, aria-invalid); .is-*
10
+ * classes as legacy fallbacks.
11
+ */
12
+
13
+ @layer vttforge.components {
14
+ /* ════════ BUTTONS ════════ */
15
+
16
+ .vttf-btn {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ gap: var(--vttf-space-2);
20
+ padding: 10px 16px;
21
+ border-radius: var(--vttf-radius-md);
22
+ font-family: var(--vttf-font-body);
23
+ font-weight: var(--vttf-text-weight-semibold);
24
+ font-size: 14px;
25
+ border: 1px solid transparent;
26
+ cursor: pointer;
27
+ transition:
28
+ transform var(--vttf-duration-fast) var(--vttf-ease-out),
29
+ box-shadow var(--vttf-duration-fast) var(--vttf-ease-out),
30
+ background var(--vttf-duration-fast) var(--vttf-ease-out),
31
+ border-color var(--vttf-duration-fast) var(--vttf-ease-out);
32
+ }
33
+
34
+ .vttf-btn:active {
35
+ transform: translateY(1px);
36
+ }
37
+
38
+ .vttf-btn[disabled],
39
+ .vttf-btn:disabled {
40
+ opacity: 0.5;
41
+ cursor: not-allowed;
42
+ pointer-events: none;
43
+ }
44
+
45
+ .vttf-btn--primary {
46
+ background: var(--vttf-ember);
47
+ color: var(--vttf-text-inverse);
48
+ box-shadow:
49
+ 0 1px 0 rgb(255 255 255 / 0.2) inset,
50
+ var(--vttf-shadow-sm);
51
+ }
52
+ .vttf-btn--primary:hover {
53
+ background: var(--vttf-ember-glow);
54
+ }
55
+
56
+ .vttf-btn--secondary {
57
+ background: var(--vttf-surface);
58
+ color: var(--vttf-text);
59
+ border-color: var(--vttf-border-strong);
60
+ }
61
+ .vttf-btn--secondary:hover {
62
+ background: var(--vttf-surface-2);
63
+ border-color: var(--vttf-text-faint);
64
+ }
65
+
66
+ .vttf-btn--ghost {
67
+ background: transparent;
68
+ color: var(--vttf-text-muted);
69
+ }
70
+ .vttf-btn--ghost:hover {
71
+ color: var(--vttf-text);
72
+ background: var(--vttf-surface);
73
+ }
74
+
75
+ .vttf-btn--danger {
76
+ background: transparent;
77
+ color: var(--vttf-rose);
78
+ border-color: color-mix(in oklch, var(--vttf-rose) 40%, transparent);
79
+ }
80
+ .vttf-btn--danger:hover {
81
+ background: color-mix(in oklch, var(--vttf-rose) 10%, transparent);
82
+ }
83
+
84
+ .vttf-btn .vttf-kbd {
85
+ font-family: var(--vttf-font-mono);
86
+ font-size: 11px;
87
+ padding: 1px 5px;
88
+ border-radius: var(--vttf-radius-sm);
89
+ background: rgb(0 0 0 / 0.25);
90
+ color: inherit;
91
+ opacity: 0.7;
92
+ }
93
+
94
+ /* ════════ BADGES ════════ */
95
+
96
+ .vttf-badge {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ gap: 6px;
100
+ padding: 2px 8px;
101
+ border-radius: var(--vttf-radius-full);
102
+ font-family: var(--vttf-font-mono);
103
+ font-size: 11px;
104
+ font-weight: 500;
105
+ border: 1px solid;
106
+ line-height: 1.5;
107
+ }
108
+
109
+ .vttf-badge__blip {
110
+ width: 6px;
111
+ height: 6px;
112
+ border-radius: var(--vttf-radius-full);
113
+ background: currentColor;
114
+ }
115
+
116
+ .vttf-badge--ember {
117
+ color: var(--vttf-ember);
118
+ border-color: color-mix(in oklch, var(--vttf-ember) 40%, transparent);
119
+ background: color-mix(in oklch, var(--vttf-ember) 8%, transparent);
120
+ }
121
+ .vttf-badge--steel {
122
+ color: var(--vttf-steel);
123
+ border-color: color-mix(in oklch, var(--vttf-steel) 40%, transparent);
124
+ background: color-mix(in oklch, var(--vttf-steel) 8%, transparent);
125
+ }
126
+ .vttf-badge--mint {
127
+ color: var(--vttf-mint);
128
+ border-color: color-mix(in oklch, var(--vttf-mint) 40%, transparent);
129
+ background: color-mix(in oklch, var(--vttf-mint) 8%, transparent);
130
+ }
131
+ .vttf-badge--gold {
132
+ color: var(--vttf-gold);
133
+ border-color: color-mix(in oklch, var(--vttf-gold) 40%, transparent);
134
+ background: color-mix(in oklch, var(--vttf-gold) 8%, transparent);
135
+ }
136
+ .vttf-badge--rose {
137
+ color: var(--vttf-rose);
138
+ border-color: color-mix(in oklch, var(--vttf-rose) 40%, transparent);
139
+ background: color-mix(in oklch, var(--vttf-rose) 8%, transparent);
140
+ }
141
+ .vttf-badge--violet {
142
+ color: var(--vttf-violet);
143
+ border-color: color-mix(in oklch, var(--vttf-violet) 40%, transparent);
144
+ background: color-mix(in oklch, var(--vttf-violet) 8%, transparent);
145
+ }
146
+ .vttf-badge--muted {
147
+ color: var(--vttf-text-muted);
148
+ border-color: var(--vttf-border);
149
+ background: var(--vttf-surface);
150
+ }
151
+
152
+ /* ════════ CODE BLOCK + SYNTAX TOKENS ════════ */
153
+
154
+ .vttf-codeblock {
155
+ background: var(--vttf-bg-sunken);
156
+ border: 1px solid var(--vttf-border);
157
+ border-radius: var(--vttf-radius-lg);
158
+ overflow: hidden;
159
+ font-family: var(--vttf-font-mono);
160
+ }
161
+
162
+ .vttf-codeblock__head {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: var(--vttf-space-3);
166
+ padding: 10px 14px;
167
+ border-bottom: 1px solid var(--vttf-border);
168
+ background: color-mix(in oklch, var(--vttf-bg-elevated) 70%, transparent);
169
+ font-size: 12px;
170
+ color: var(--vttf-text-muted);
171
+ }
172
+
173
+ .vttf-codeblock__dots {
174
+ display: flex;
175
+ gap: 6px;
176
+ }
177
+ .vttf-codeblock__dots span {
178
+ width: 10px;
179
+ height: 10px;
180
+ border-radius: var(--vttf-radius-full);
181
+ background: var(--vttf-border-strong);
182
+ }
183
+
184
+ .vttf-codeblock__file {
185
+ color: var(--vttf-text);
186
+ background: var(--vttf-surface);
187
+ padding: 4px 10px;
188
+ border-radius: var(--vttf-radius-sm);
189
+ border: 1px solid var(--vttf-border);
190
+ }
191
+
192
+ .vttf-codeblock__lang {
193
+ margin-left: auto;
194
+ font-size: 11px;
195
+ letter-spacing: 0.08em;
196
+ text-transform: uppercase;
197
+ color: var(--vttf-text-faint);
198
+ }
199
+
200
+ .vttf-codeblock pre {
201
+ margin: 0;
202
+ padding: var(--vttf-space-5);
203
+ font-size: 13.5px;
204
+ line-height: 1.6;
205
+ color: var(--vttf-text);
206
+ overflow-x: auto;
207
+ }
208
+
209
+ .vttf-tk-com {
210
+ color: var(--vttf-text-faint);
211
+ font-style: italic;
212
+ }
213
+ .vttf-tk-kw {
214
+ color: var(--vttf-violet);
215
+ }
216
+ .vttf-tk-fn {
217
+ color: var(--vttf-ember-glow);
218
+ }
219
+ .vttf-tk-str {
220
+ color: var(--vttf-mint);
221
+ }
222
+ .vttf-tk-num {
223
+ color: var(--vttf-gold);
224
+ }
225
+ .vttf-tk-ty {
226
+ color: var(--vttf-steel);
227
+ }
228
+ .vttf-tk-pun {
229
+ color: var(--vttf-text-muted);
230
+ }
231
+ .vttf-tk-prop {
232
+ color: var(--vttf-text);
233
+ }
234
+
235
+ /* ════════ FORM GRID + FIELD ════════ */
236
+
237
+ .vttf-form-grid {
238
+ display: grid;
239
+ grid-template-columns: repeat(2, 1fr);
240
+ gap: var(--vttf-space-6);
241
+ }
242
+
243
+ .vttf-form-field {
244
+ display: flex;
245
+ flex-direction: column;
246
+ gap: 6px;
247
+ }
248
+
249
+ .vttf-form-field > label {
250
+ font-family: var(--vttf-font-mono);
251
+ font-size: 11px;
252
+ text-transform: uppercase;
253
+ letter-spacing: 0.1em;
254
+ color: var(--vttf-text-faint);
255
+ }
256
+
257
+ .vttf-form-field .vttf-hint {
258
+ font-size: 12px;
259
+ color: var(--vttf-text-muted);
260
+ }
261
+
262
+ /* ════════ TEXT INPUTS ════════ */
263
+
264
+ .vttf-input,
265
+ .vttf-textarea,
266
+ .vttf-select {
267
+ background: var(--vttf-bg-sunken);
268
+ border: 1px solid var(--vttf-border);
269
+ color: var(--vttf-text);
270
+ padding: 9px 12px;
271
+ border-radius: var(--vttf-radius-sm);
272
+ font-family: var(--vttf-font-body);
273
+ font-size: 14px;
274
+ width: 100%;
275
+ transition:
276
+ border-color var(--vttf-duration-fast) var(--vttf-ease-out),
277
+ box-shadow var(--vttf-duration-fast) var(--vttf-ease-out);
278
+ }
279
+
280
+ .vttf-input:focus-visible,
281
+ .vttf-textarea:focus-visible,
282
+ .vttf-select:focus-visible {
283
+ border-color: var(--vttf-ember);
284
+ }
285
+
286
+ .vttf-input[aria-invalid="true"],
287
+ .vttf-input.is-invalid,
288
+ .vttf-textarea[aria-invalid="true"],
289
+ .vttf-textarea.is-invalid,
290
+ .vttf-select[aria-invalid="true"],
291
+ .vttf-select.is-invalid {
292
+ border-color: var(--vttf-rose);
293
+ box-shadow: 0 0 0 3px color-mix(in oklch, var(--vttf-rose) 18%, transparent);
294
+ }
295
+
296
+ .vttf-textarea {
297
+ min-height: 96px;
298
+ resize: vertical;
299
+ font-family: var(--vttf-font-mono);
300
+ font-size: 13px;
301
+ }
302
+
303
+ .vttf-select {
304
+ appearance: none;
305
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none'><path d='M3 5l3 3 3-3' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
306
+ background-repeat: no-repeat;
307
+ background-position: right 10px center;
308
+ padding-right: 32px;
309
+ }
310
+
311
+ /* ════════ CHECKBOXES + RADIOS ════════ */
312
+
313
+ .vttf-check,
314
+ .vttf-radio {
315
+ display: inline-flex;
316
+ align-items: center;
317
+ gap: var(--vttf-space-2);
318
+ cursor: pointer;
319
+ font-size: 14px;
320
+ }
321
+
322
+ .vttf-check input,
323
+ .vttf-radio input {
324
+ position: absolute;
325
+ opacity: 0;
326
+ pointer-events: none;
327
+ }
328
+
329
+ .vttf-check__box,
330
+ .vttf-radio__box {
331
+ width: 16px;
332
+ height: 16px;
333
+ background: var(--vttf-bg-sunken);
334
+ border: 1.5px solid var(--vttf-border-strong);
335
+ display: inline-flex;
336
+ align-items: center;
337
+ justify-content: center;
338
+ flex: 0 0 auto;
339
+ transition:
340
+ background var(--vttf-duration-fast) var(--vttf-ease-out),
341
+ border-color var(--vttf-duration-fast) var(--vttf-ease-out);
342
+ }
343
+
344
+ .vttf-check__box {
345
+ border-radius: var(--vttf-radius-sm);
346
+ }
347
+ .vttf-radio__box {
348
+ border-radius: var(--vttf-radius-full);
349
+ }
350
+
351
+ .vttf-check input:checked + .vttf-check__box {
352
+ background: var(--vttf-ember);
353
+ border-color: var(--vttf-ember);
354
+ }
355
+ .vttf-check input:checked + .vttf-check__box::after {
356
+ content: "";
357
+ width: 9px;
358
+ height: 5px;
359
+ border-left: 2px solid var(--vttf-text-inverse);
360
+ border-bottom: 2px solid var(--vttf-text-inverse);
361
+ transform: rotate(-45deg) translate(1px, -1px);
362
+ }
363
+
364
+ .vttf-radio input:checked + .vttf-radio__box {
365
+ border-color: var(--vttf-ember);
366
+ }
367
+ .vttf-radio input:checked + .vttf-radio__box::after {
368
+ content: "";
369
+ width: 8px;
370
+ height: 8px;
371
+ border-radius: var(--vttf-radius-full);
372
+ background: var(--vttf-ember);
373
+ }
374
+
375
+ .vttf-check input:focus-visible + .vttf-check__box,
376
+ .vttf-radio input:focus-visible + .vttf-radio__box {
377
+ box-shadow: var(--vttf-focus-ring);
378
+ }
379
+
380
+ /* ════════ SWITCH ════════ */
381
+
382
+ .vttf-switch {
383
+ display: inline-flex;
384
+ align-items: center;
385
+ gap: var(--vttf-space-3);
386
+ cursor: pointer;
387
+ font-size: 14px;
388
+ }
389
+
390
+ .vttf-switch input {
391
+ position: absolute;
392
+ opacity: 0;
393
+ pointer-events: none;
394
+ }
395
+
396
+ .vttf-switch__track {
397
+ width: 36px;
398
+ height: 20px;
399
+ border-radius: var(--vttf-radius-full);
400
+ background: var(--vttf-surface-2);
401
+ border: 1px solid var(--vttf-border-strong);
402
+ position: relative;
403
+ transition:
404
+ background var(--vttf-duration-base) var(--vttf-ease-out),
405
+ border-color var(--vttf-duration-base) var(--vttf-ease-out);
406
+ }
407
+
408
+ .vttf-switch__track::after {
409
+ content: "";
410
+ position: absolute;
411
+ width: 14px;
412
+ height: 14px;
413
+ top: 2px;
414
+ left: 2px;
415
+ background: var(--vttf-text-muted);
416
+ border-radius: var(--vttf-radius-full);
417
+ transition:
418
+ transform var(--vttf-duration-base) var(--vttf-ease-out),
419
+ background var(--vttf-duration-base) var(--vttf-ease-out);
420
+ }
421
+
422
+ .vttf-switch input:checked + .vttf-switch__track {
423
+ background: var(--vttf-ember);
424
+ border-color: var(--vttf-ember);
425
+ }
426
+
427
+ .vttf-switch input:checked + .vttf-switch__track::after {
428
+ transform: translateX(16px);
429
+ background: var(--vttf-text-inverse);
430
+ }
431
+
432
+ .vttf-switch input:focus-visible + .vttf-switch__track {
433
+ box-shadow: var(--vttf-focus-ring);
434
+ }
435
+
436
+ /* ════════ RANGE ════════ */
437
+
438
+ .vttf-range {
439
+ appearance: none;
440
+ -webkit-appearance: none;
441
+ width: 100%;
442
+ height: 4px;
443
+ background: var(--vttf-surface-2);
444
+ border-radius: var(--vttf-radius-full);
445
+ outline: none;
446
+ }
447
+
448
+ .vttf-range::-webkit-slider-thumb {
449
+ appearance: none;
450
+ -webkit-appearance: none;
451
+ width: 16px;
452
+ height: 16px;
453
+ border-radius: var(--vttf-radius-full);
454
+ background: var(--vttf-ember);
455
+ border: 2px solid var(--vttf-bg);
456
+ box-shadow: var(--vttf-shadow-sm);
457
+ cursor: pointer;
458
+ }
459
+
460
+ .vttf-range::-moz-range-thumb {
461
+ width: 14px;
462
+ height: 14px;
463
+ border-radius: var(--vttf-radius-full);
464
+ background: var(--vttf-ember);
465
+ border: 2px solid var(--vttf-bg);
466
+ cursor: pointer;
467
+ }
468
+
469
+ /* ════════ SEGMENTED CONTROL ════════ */
470
+
471
+ .vttf-segment {
472
+ display: inline-flex;
473
+ padding: 3px;
474
+ background: var(--vttf-surface);
475
+ border: 1px solid var(--vttf-border);
476
+ border-radius: var(--vttf-radius-md);
477
+ }
478
+
479
+ .vttf-segment button {
480
+ border: none;
481
+ background: transparent;
482
+ color: var(--vttf-text-muted);
483
+ font-family: var(--vttf-font-body);
484
+ font-size: 13px;
485
+ font-weight: 500;
486
+ padding: 6px 12px;
487
+ border-radius: var(--vttf-radius-sm);
488
+ cursor: pointer;
489
+ transition:
490
+ background var(--vttf-duration-fast) var(--vttf-ease-out),
491
+ color var(--vttf-duration-fast) var(--vttf-ease-out),
492
+ box-shadow var(--vttf-duration-fast) var(--vttf-ease-out);
493
+ }
494
+
495
+ .vttf-segment button:hover {
496
+ color: var(--vttf-text);
497
+ }
498
+
499
+ .vttf-segment button[aria-pressed="true"],
500
+ .vttf-segment button[aria-selected="true"],
501
+ .vttf-segment button.is-active {
502
+ background: var(--vttf-bg-sunken);
503
+ color: var(--vttf-text);
504
+ box-shadow: var(--vttf-shadow-sm);
505
+ }
506
+
507
+ /* ════════ TABS ════════ */
508
+
509
+ .vttf-tabs {
510
+ display: flex;
511
+ gap: 4px;
512
+ padding: 0 var(--vttf-space-4);
513
+ border-bottom: 1px solid var(--vttf-border);
514
+ background: var(--vttf-bg-elevated);
515
+ }
516
+
517
+ .vttf-tab {
518
+ padding: 10px 14px;
519
+ font-family: var(--vttf-font-body);
520
+ font-size: 13px;
521
+ font-weight: 500;
522
+ color: var(--vttf-text-muted);
523
+ border-bottom: 2px solid transparent;
524
+ margin-bottom: -1px;
525
+ cursor: pointer;
526
+ transition:
527
+ color var(--vttf-duration-fast) var(--vttf-ease-out),
528
+ border-color var(--vttf-duration-fast) var(--vttf-ease-out);
529
+ }
530
+
531
+ .vttf-tab:hover {
532
+ color: var(--vttf-text);
533
+ }
534
+
535
+ .vttf-tab[aria-selected="true"],
536
+ .vttf-tab.is-active {
537
+ color: var(--vttf-text);
538
+ border-bottom-color: var(--vttf-ember);
539
+ }
540
+
541
+ .vttf-tab__count {
542
+ font-family: var(--vttf-font-mono);
543
+ font-size: 10px;
544
+ margin-left: 6px;
545
+ color: var(--vttf-text-faint);
546
+ }
547
+
548
+ /* ════════ SHEET CONTAINER ════════ */
549
+
550
+ .vttf-sheet {
551
+ background: var(--vttf-bg-elevated);
552
+ border: 1px solid var(--vttf-border);
553
+ border-radius: var(--vttf-radius-lg);
554
+ overflow: hidden;
555
+ }
556
+
557
+ .vttf-sheet__head {
558
+ padding: var(--vttf-space-4);
559
+ display: flex;
560
+ align-items: center;
561
+ gap: var(--vttf-space-4);
562
+ border-bottom: 1px solid var(--vttf-border);
563
+ background: var(--vttf-surface);
564
+ }
565
+
566
+ .vttf-sheet__title {
567
+ font-family: var(--vttf-font-display);
568
+ font-size: 22px;
569
+ font-weight: var(--vttf-text-weight-semibold);
570
+ letter-spacing: -0.02em;
571
+ }
572
+
573
+ .vttf-sheet__sub {
574
+ font-family: var(--vttf-font-mono);
575
+ font-size: 11px;
576
+ color: var(--vttf-text-faint);
577
+ text-transform: uppercase;
578
+ letter-spacing: 0.1em;
579
+ }
580
+
581
+ .vttf-sheet__body {
582
+ padding: var(--vttf-space-5);
583
+ display: grid;
584
+ gap: var(--vttf-space-3);
585
+ }
586
+
587
+ .vttf-portrait {
588
+ width: 56px;
589
+ height: 56px;
590
+ border-radius: var(--vttf-radius-md);
591
+ background:
592
+ repeating-linear-gradient(
593
+ 45deg,
594
+ color-mix(in oklch, var(--vttf-ember-deep) 18%, transparent) 0 6px,
595
+ color-mix(in oklch, var(--vttf-ember-deep) 8%, transparent) 6px 12px
596
+ ),
597
+ var(--vttf-surface-2);
598
+ border: 1px dashed var(--vttf-border-strong);
599
+ display: flex;
600
+ align-items: center;
601
+ justify-content: center;
602
+ font-family: var(--vttf-font-mono);
603
+ font-size: 10px;
604
+ color: var(--vttf-text-faint);
605
+ }
606
+
607
+ .vttf-field-row {
608
+ display: grid;
609
+ grid-template-columns: 120px 1fr;
610
+ gap: var(--vttf-space-4);
611
+ align-items: center;
612
+ }
613
+
614
+ .vttf-field-label {
615
+ font-family: var(--vttf-font-mono);
616
+ font-size: 11px;
617
+ text-transform: uppercase;
618
+ letter-spacing: 0.08em;
619
+ color: var(--vttf-text-faint);
620
+ }
621
+
622
+ /* ════════ STAT PILL ════════ */
623
+
624
+ .vttf-stat-pill {
625
+ display: inline-flex;
626
+ align-items: center;
627
+ gap: var(--vttf-space-2);
628
+ padding: 4px 10px 4px 4px;
629
+ background: var(--vttf-surface);
630
+ border-radius: var(--vttf-radius-full);
631
+ border: 1px solid var(--vttf-border);
632
+ }
633
+
634
+ .vttf-stat-pill__n {
635
+ width: 28px;
636
+ height: 28px;
637
+ border-radius: var(--vttf-radius-full);
638
+ background: var(--vttf-ember);
639
+ color: var(--vttf-text-inverse);
640
+ display: flex;
641
+ align-items: center;
642
+ justify-content: center;
643
+ font-family: var(--vttf-font-mono);
644
+ font-weight: var(--vttf-text-weight-bold);
645
+ font-size: 13px;
646
+ }
647
+
648
+ .vttf-stat-pill__label {
649
+ font-size: 12px;
650
+ color: var(--vttf-text-muted);
651
+ font-family: var(--vttf-font-mono);
652
+ text-transform: uppercase;
653
+ letter-spacing: 0.08em;
654
+ }
655
+
656
+ /* ════════ DROPZONE ════════ */
657
+
658
+ .vttf-dropzone {
659
+ border: 1.5px dashed var(--vttf-border-strong);
660
+ border-radius: var(--vttf-radius-md);
661
+ padding: var(--vttf-space-6);
662
+ display: flex;
663
+ flex-direction: column;
664
+ align-items: center;
665
+ gap: var(--vttf-space-2);
666
+ color: var(--vttf-text-muted);
667
+ background: color-mix(in oklch, var(--vttf-bg) 40%, transparent);
668
+ transition:
669
+ background var(--vttf-duration-fast) var(--vttf-ease-out),
670
+ border-color var(--vttf-duration-fast) var(--vttf-ease-out);
671
+ }
672
+
673
+ .vttf-dropzone.is-active,
674
+ .vttf-dropzone[data-drop-effect] {
675
+ border-color: var(--vttf-ember);
676
+ color: var(--vttf-ember);
677
+ background: color-mix(in oklch, var(--vttf-ember) 6%, transparent);
678
+ box-shadow: var(--vttf-glow-ember);
679
+ }
680
+
681
+ .vttf-dropzone__icon {
682
+ font-family: var(--vttf-font-mono);
683
+ font-size: 24px;
684
+ }
685
+
686
+ .vttf-dropzone__title {
687
+ font-family: var(--vttf-font-body);
688
+ font-weight: var(--vttf-text-weight-semibold);
689
+ font-size: 14px;
690
+ color: var(--vttf-text);
691
+ }
692
+
693
+ .vttf-dropzone__sub {
694
+ font-family: var(--vttf-font-mono);
695
+ font-size: 11px;
696
+ }
697
+
698
+ /* ════════ ITEM ROW ════════ */
699
+
700
+ .vttf-item-row {
701
+ display: grid;
702
+ grid-template-columns: 24px 32px 1fr auto auto;
703
+ align-items: center;
704
+ gap: var(--vttf-space-3);
705
+ padding: 8px 10px;
706
+ border: 1px solid var(--vttf-border);
707
+ border-radius: var(--vttf-radius-sm);
708
+ background: var(--vttf-surface);
709
+ }
710
+
711
+ .vttf-item-row + .vttf-item-row {
712
+ margin-top: -1px;
713
+ }
714
+
715
+ .vttf-item-row__grip {
716
+ color: var(--vttf-text-faint);
717
+ cursor: grab;
718
+ font-family: var(--vttf-font-mono);
719
+ font-size: 14px;
720
+ }
721
+
722
+ .vttf-item-row__thumb {
723
+ width: 32px;
724
+ height: 32px;
725
+ border-radius: var(--vttf-radius-sm);
726
+ background: var(--vttf-surface-2);
727
+ border: 1px solid var(--vttf-border);
728
+ display: flex;
729
+ align-items: center;
730
+ justify-content: center;
731
+ font-family: var(--vttf-font-mono);
732
+ font-size: 12px;
733
+ color: var(--vttf-text-faint);
734
+ }
735
+
736
+ .vttf-item-row__name {
737
+ font-size: 14px;
738
+ font-weight: 500;
739
+ }
740
+
741
+ .vttf-item-row__meta {
742
+ font-family: var(--vttf-font-mono);
743
+ font-size: 11px;
744
+ color: var(--vttf-text-faint);
745
+ }
746
+ }
@@ -0,0 +1,97 @@
1
+ /*!
2
+ * @vttforge/styles — tokens.css
3
+ * Auto-generated from tokens.json. Do not edit by hand.
4
+ *
5
+ * Token strategy:
6
+ * :root — Forge default (dark, warm, ember accent)
7
+ * [data-theme="light"] — Forge light overrides
8
+ * [data-theme="foundry"] — Opt-in mapping to Foundry v13 Theme V2
9
+ */
10
+
11
+ @layer vttforge.tokens {
12
+ :root {
13
+ --vttf-bg: oklch(0.165 0.012 50);
14
+ --vttf-bg-elevated: oklch(0.205 0.013 50);
15
+ --vttf-bg-sunken: oklch(0.125 0.010 50);
16
+ --vttf-surface: oklch(0.235 0.014 50);
17
+ --vttf-surface-2: oklch(0.275 0.014 50);
18
+ --vttf-border: oklch(0.320 0.014 50);
19
+ --vttf-border-strong: oklch(0.420 0.018 50);
20
+ --vttf-text: oklch(0.965 0.008 80);
21
+ --vttf-text-muted: oklch(0.745 0.012 70);
22
+ --vttf-text-faint: oklch(0.540 0.012 60);
23
+ --vttf-text-inverse: oklch(0.165 0.012 50);
24
+ --vttf-ember: oklch(0.715 0.170 48);
25
+ --vttf-ember-deep: oklch(0.520 0.160 38);
26
+ --vttf-ember-glow: oklch(0.815 0.130 60);
27
+ --vttf-steel: oklch(0.700 0.140 235);
28
+ --vttf-mint: oklch(0.745 0.130 165);
29
+ --vttf-gold: oklch(0.825 0.135 90);
30
+ --vttf-rose: oklch(0.685 0.175 18);
31
+ --vttf-violet: oklch(0.690 0.150 295);
32
+ --vttf-space-1: 4px;
33
+ --vttf-space-2: 8px;
34
+ --vttf-space-3: 12px;
35
+ --vttf-space-4: 16px;
36
+ --vttf-space-5: 20px;
37
+ --vttf-space-6: 24px;
38
+ --vttf-space-8: 32px;
39
+ --vttf-space-10: 40px;
40
+ --vttf-space-12: 48px;
41
+ --vttf-space-16: 64px;
42
+ --vttf-radius-sm: 4px;
43
+ --vttf-radius-md: 6px;
44
+ --vttf-radius-lg: 10px;
45
+ --vttf-radius-xl: 16px;
46
+ --vttf-radius-full: 999px;
47
+ --vttf-shadow-sm: 0px 1px 2px 0px rgb(0 0 0 / 0.35);
48
+ --vttf-shadow-md: 0px 6px 14px -6px rgb(0 0 0 / 0.55), 0px 1px 2px 0px rgb(0 0 0 / 0.35);
49
+ --vttf-shadow-lg: 0px 24px 40px -18px rgb(0 0 0 / 0.6), 0px 4px 8px -2px rgb(0 0 0 / 0.4);
50
+ --vttf-font-display: "Bricolage Grotesque", system-ui, sans-serif;
51
+ --vttf-font-body: "Hanken Grotesk", system-ui, sans-serif;
52
+ --vttf-font-mono: "JetBrains Mono", ui-monospace, monospace;
53
+ --vttf-text-size-display-1: 72px;
54
+ --vttf-text-size-display-2: 48px;
55
+ --vttf-text-size-h: 28px;
56
+ --vttf-text-size-lead: 19px;
57
+ --vttf-text-size-body: 16px;
58
+ --vttf-text-size-small: 13px;
59
+ --vttf-text-size-meta: 11px;
60
+ --vttf-text-weight-body: 400;
61
+ --vttf-text-weight-semibold: 600;
62
+ --vttf-text-weight-bold: 700;
63
+ --vttf-duration-fast: 120ms;
64
+ --vttf-duration-base: 180ms;
65
+ --vttf-duration-slow: 320ms;
66
+ --vttf-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
67
+ --vttf-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
68
+ --vttf-glow-ember: 0 0 0 1px oklch(0.715 0.170 48 / 0.5), 0 0 24px -4px oklch(0.715 0.170 48 / 0.35);
69
+ --vttf-focus-ring: 0 0 0 2px oklch(0.715 0.170 48 / 0.45);
70
+ }
71
+
72
+ [data-theme="light"] {
73
+ --vttf-bg: oklch(0.985 0.004 80);
74
+ --vttf-bg-elevated: oklch(0.972 0.006 78);
75
+ --vttf-bg-sunken: oklch(0.945 0.010 72);
76
+ --vttf-surface: oklch(0.962 0.008 75);
77
+ --vttf-surface-2: oklch(0.928 0.012 68);
78
+ --vttf-border: oklch(0.880 0.014 62);
79
+ --vttf-border-strong: oklch(0.760 0.016 58);
80
+ --vttf-text: oklch(0.215 0.020 42);
81
+ --vttf-text-muted: oklch(0.430 0.018 48);
82
+ --vttf-text-faint: oklch(0.595 0.014 55);
83
+ --vttf-ember: oklch(0.565 0.180 36);
84
+ --vttf-ember-deep: oklch(0.430 0.160 30);
85
+ --vttf-ember-glow: oklch(0.670 0.170 42);
86
+ }
87
+
88
+ [data-theme="foundry"] {
89
+ --vttf-text: var(--color-text-primary, oklch(0.965 0.008 80));
90
+ --vttf-text-muted: var(--color-text-secondary, oklch(0.745 0.012 70));
91
+ --vttf-bg: var(--color-bg-primary, oklch(0.165 0.012 50));
92
+ --vttf-bg-elevated: var(--color-bg-option, oklch(0.205 0.013 50));
93
+ --vttf-border: var(--color-border-light-primary, oklch(0.320 0.014 50));
94
+ --vttf-font-body: var(--font-primary, "Hanken Grotesk", system-ui, sans-serif);
95
+ --vttf-font-mono: var(--font-monospace, "JetBrains Mono", ui-monospace, monospace);
96
+ }
97
+ }
package/index.css ADDED
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * @vttforge/styles
3
+ *
4
+ * One-line import for consumers that want everything VTTForge ships, in the right
5
+ * cascade-layer order.
6
+ *
7
+ * @import '@vttforge/styles';
8
+ *
9
+ * Layer order matches Foundry v13's outer layer: tokens → reset → base → components → themes.
10
+ * Foundry's own runtime wraps system CSS in its top-level `system` layer.
11
+ */
12
+
13
+ @layer vttforge.tokens, vttforge.reset, vttforge.base, vttforge.components, vttforge.themes;
14
+
15
+ @import "./dist/tokens.css";
16
+ @import "./reset.css";
17
+ @import "./base.css";
18
+ @import "./components.css";
19
+ @import "./themes/forge.css";
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@vttforge/styles",
3
+ "version": "0.3.0",
4
+ "description": "VTTForge base CSS layer: design tokens, reset, base, components, opt-in themes (CSS Cascade Layers).",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "homepage": "https://github.com/vttforge/vttforge#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/vttforge/vttforge.git",
11
+ "directory": "packages/styles"
12
+ },
13
+ "bugs": "https://github.com/vttforge/vttforge/issues",
14
+ "keywords": [
15
+ "foundryvtt",
16
+ "css",
17
+ "cascade-layers",
18
+ "design-tokens"
19
+ ],
20
+ "exports": {
21
+ ".": "./index.css",
22
+ "./tokens.css": "./dist/tokens.css",
23
+ "./reset.css": "./reset.css",
24
+ "./base.css": "./base.css",
25
+ "./components.css": "./components.css",
26
+ "./styles.layer.css": "./styles.layer.css",
27
+ "./themes/forge.css": "./themes/forge.css",
28
+ "./tokens.json": "./tokens.json",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "*.css",
33
+ "dist/*.css",
34
+ "themes/*.css",
35
+ "tokens.json",
36
+ "README.md",
37
+ "CHANGELOG.md"
38
+ ],
39
+ "scripts": {
40
+ "build": "node build-tokens.mjs",
41
+ "typecheck": "echo 'styles is CSS-only — no typecheck'",
42
+ "test": "echo 'styles has no tests yet'",
43
+ "publint": "publint",
44
+ "attw": "echo 'styles is CSS-only — no types'"
45
+ },
46
+ "devDependencies": {
47
+ "publint": "catalog:",
48
+ "style-dictionary": "catalog:"
49
+ },
50
+ "engines": {
51
+ "node": ">=26.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public",
55
+ "provenance": false
56
+ }
57
+ }
package/reset.css ADDED
@@ -0,0 +1,61 @@
1
+ /*!
2
+ * @vttforge/styles — reset.css
3
+ *
4
+ * Scoped reset that complements Foundry's own `reset` layer without overriding it.
5
+ * Targets only VTTForge-marked roots: `.vttf-app`, `.vttf-sheet`, `form[data-vttf]`.
6
+ * Outside those scopes, Foundry's reset is untouched.
7
+ */
8
+
9
+ @layer vttforge.reset {
10
+ .vttf-app,
11
+ .vttf-app *,
12
+ .vttf-app *::before,
13
+ .vttf-app *::after,
14
+ .vttf-sheet,
15
+ .vttf-sheet *,
16
+ .vttf-sheet *::before,
17
+ .vttf-sheet *::after,
18
+ form[data-vttf],
19
+ form[data-vttf] *,
20
+ form[data-vttf] *::before,
21
+ form[data-vttf] *::after {
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ .vttf-app,
26
+ .vttf-sheet {
27
+ margin: 0;
28
+ }
29
+
30
+ .vttf-app button,
31
+ .vttf-sheet button,
32
+ form[data-vttf] button {
33
+ font: inherit;
34
+ color: inherit;
35
+ background: none;
36
+ border: none;
37
+ padding: 0;
38
+ }
39
+
40
+ .vttf-app input,
41
+ .vttf-sheet input,
42
+ .vttf-app textarea,
43
+ .vttf-sheet textarea,
44
+ .vttf-app select,
45
+ .vttf-sheet select,
46
+ form[data-vttf] input,
47
+ form[data-vttf] textarea,
48
+ form[data-vttf] select {
49
+ font: inherit;
50
+ color: inherit;
51
+ }
52
+
53
+ .vttf-app ul,
54
+ .vttf-app ol,
55
+ .vttf-sheet ul,
56
+ .vttf-sheet ol {
57
+ list-style: none;
58
+ margin: 0;
59
+ padding: 0;
60
+ }
61
+ }
@@ -0,0 +1,19 @@
1
+ /*!
2
+ * @vttforge/styles — styles.layer.css
3
+ *
4
+ * Mantine-style variant: wraps `index.css` inside an explicit `@layer vttforge`
5
+ * for consumers that want a single namespaced layer instead of the five
6
+ * sub-layers (`vttforge.tokens`, `vttforge.reset`, `vttforge.base`,
7
+ * `vttforge.components`, `vttforge.themes`).
8
+ *
9
+ * Use this entry when you need to control VTTForge's cascade position relative
10
+ * to your own layers; use the default `index.css` otherwise.
11
+ */
12
+
13
+ @layer vttforge {
14
+ @import "./dist/tokens.css";
15
+ @import "./reset.css";
16
+ @import "./base.css";
17
+ @import "./components.css";
18
+ @import "./themes/forge.css";
19
+ }
@@ -0,0 +1,31 @@
1
+ /*!
2
+ * @vttforge/styles — themes/forge.css
3
+ *
4
+ * Auto-light support for the Forge theme. When a consumer sets
5
+ * `data-theme="auto"` on the sheet root, the GM's OS-level light/dark
6
+ * preference takes over. Explicit `data-theme="light"` and
7
+ * `data-theme="foundry"` are handled by tokens.css.
8
+ *
9
+ * Imported by `index.css` and `styles.layer.css` after components, so theme
10
+ * overrides win cleanly inside `@layer vttforge.themes`.
11
+ */
12
+
13
+ @layer vttforge.themes {
14
+ @media (prefers-color-scheme: light) {
15
+ :where([data-theme="auto"]) {
16
+ --vttf-bg: oklch(0.985 0.004 80);
17
+ --vttf-bg-elevated: oklch(0.972 0.006 78);
18
+ --vttf-bg-sunken: oklch(0.945 0.01 72);
19
+ --vttf-surface: oklch(0.962 0.008 75);
20
+ --vttf-surface-2: oklch(0.928 0.012 68);
21
+ --vttf-border: oklch(0.88 0.014 62);
22
+ --vttf-border-strong: oklch(0.76 0.016 58);
23
+ --vttf-text: oklch(0.215 0.02 42);
24
+ --vttf-text-muted: oklch(0.43 0.018 48);
25
+ --vttf-text-faint: oklch(0.595 0.014 55);
26
+ --vttf-ember: oklch(0.565 0.18 36);
27
+ --vttf-ember-deep: oklch(0.43 0.16 30);
28
+ --vttf-ember-glow: oklch(0.67 0.17 42);
29
+ }
30
+ }
31
+ }
package/tokens.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "$schema": "https://design-tokens.github.io/community-group/format/",
3
+ "$description": "VTTForge design tokens · W3C Design Tokens Community Group format. Import into Figma via the Tokens Studio plugin, or generate framework-specific code with Style Dictionary.",
4
+ "color": {
5
+ "surface": {
6
+ "bg": { "$type": "color", "$value": "oklch(0.165 0.012 50)", "$description": "Page background (dark)" },
7
+ "bg-elevated": { "$type": "color", "$value": "oklch(0.205 0.013 50)", "$description": "Card / sheet" },
8
+ "bg-sunken": { "$type": "color", "$value": "oklch(0.125 0.010 50)", "$description": "Code, inputs" },
9
+ "surface": { "$type": "color", "$value": "oklch(0.235 0.014 50)", "$description": "Item rows, chips" },
10
+ "surface-2": { "$type": "color", "$value": "oklch(0.275 0.014 50)", "$description": "Hover state" },
11
+ "border": { "$type": "color", "$value": "oklch(0.320 0.014 50)" },
12
+ "border-strong": { "$type": "color", "$value": "oklch(0.420 0.018 50)" }
13
+ },
14
+ "text": {
15
+ "text": { "$type": "color", "$value": "oklch(0.965 0.008 80)", "$description": "Primary text" },
16
+ "text-muted": { "$type": "color", "$value": "oklch(0.745 0.012 70)" },
17
+ "text-faint": { "$type": "color", "$value": "oklch(0.540 0.012 60)" },
18
+ "text-inverse": { "$type": "color", "$value": "oklch(0.165 0.012 50)", "$description": "Text on ember" }
19
+ },
20
+ "accent": {
21
+ "ember": { "$type": "color", "$value": "oklch(0.715 0.170 48)", "$description": "Primary — forge ember" },
22
+ "ember-deep": { "$type": "color", "$value": "oklch(0.520 0.160 38)" },
23
+ "ember-glow": { "$type": "color", "$value": "oklch(0.815 0.130 60)" },
24
+ "steel": { "$type": "color", "$value": "oklch(0.700 0.140 235)", "$description": "Secondary — cooled steel" },
25
+ "mint": { "$type": "color", "$value": "oklch(0.745 0.130 165)", "$description": "Success" },
26
+ "gold": { "$type": "color", "$value": "oklch(0.825 0.135 90)", "$description": "Warning" },
27
+ "rose": { "$type": "color", "$value": "oklch(0.685 0.175 18)", "$description": "Danger" },
28
+ "violet": { "$type": "color", "$value": "oklch(0.690 0.150 295)", "$description": "Info / system" }
29
+ },
30
+ "light-mode": {
31
+ "$description": "Light theme overrides — apply when [data-theme=\"light\"].",
32
+ "bg": { "$type": "color", "$value": "oklch(0.985 0.004 80)" },
33
+ "bg-elevated": { "$type": "color", "$value": "oklch(0.972 0.006 78)" },
34
+ "bg-sunken": { "$type": "color", "$value": "oklch(0.945 0.010 72)" },
35
+ "surface": { "$type": "color", "$value": "oklch(0.962 0.008 75)" },
36
+ "surface-2": { "$type": "color", "$value": "oklch(0.928 0.012 68)" },
37
+ "border": { "$type": "color", "$value": "oklch(0.880 0.014 62)" },
38
+ "border-strong": { "$type": "color", "$value": "oklch(0.760 0.016 58)" },
39
+ "text": { "$type": "color", "$value": "oklch(0.215 0.020 42)" },
40
+ "text-muted": { "$type": "color", "$value": "oklch(0.430 0.018 48)" },
41
+ "text-faint": { "$type": "color", "$value": "oklch(0.595 0.014 55)" },
42
+ "ember": { "$type": "color", "$value": "oklch(0.565 0.180 36)" },
43
+ "ember-deep": { "$type": "color", "$value": "oklch(0.430 0.160 30)" },
44
+ "ember-glow": { "$type": "color", "$value": "oklch(0.670 0.170 42)" }
45
+ }
46
+ },
47
+ "space": {
48
+ "1": { "$type": "dimension", "$value": "4px" },
49
+ "2": { "$type": "dimension", "$value": "8px" },
50
+ "3": { "$type": "dimension", "$value": "12px" },
51
+ "4": { "$type": "dimension", "$value": "16px" },
52
+ "5": { "$type": "dimension", "$value": "20px" },
53
+ "6": { "$type": "dimension", "$value": "24px" },
54
+ "8": { "$type": "dimension", "$value": "32px" },
55
+ "10": { "$type": "dimension", "$value": "40px" },
56
+ "12": { "$type": "dimension", "$value": "48px" },
57
+ "16": { "$type": "dimension", "$value": "64px" }
58
+ },
59
+ "radius": {
60
+ "sm": { "$type": "dimension", "$value": "4px" },
61
+ "md": { "$type": "dimension", "$value": "6px" },
62
+ "lg": { "$type": "dimension", "$value": "10px" },
63
+ "xl": { "$type": "dimension", "$value": "16px" },
64
+ "full": { "$type": "dimension", "$value": "999px" }
65
+ },
66
+ "shadow": {
67
+ "sm": { "$type": "shadow", "$value": [
68
+ { "color": "rgb(0 0 0 / 0.35)", "offsetX": "0px", "offsetY": "1px", "blur": "2px", "spread": "0px" }
69
+ ]},
70
+ "md": { "$type": "shadow", "$value": [
71
+ { "color": "rgb(0 0 0 / 0.55)", "offsetX": "0px", "offsetY": "6px", "blur": "14px", "spread": "-6px" },
72
+ { "color": "rgb(0 0 0 / 0.35)", "offsetX": "0px", "offsetY": "1px", "blur": "2px", "spread": "0px" }
73
+ ]},
74
+ "lg": { "$type": "shadow", "$value": [
75
+ { "color": "rgb(0 0 0 / 0.6)", "offsetX": "0px", "offsetY": "24px", "blur": "40px", "spread": "-18px" },
76
+ { "color": "rgb(0 0 0 / 0.4)", "offsetX": "0px", "offsetY": "4px", "blur": "8px", "spread": "-2px" }
77
+ ]}
78
+ },
79
+ "type": {
80
+ "family": {
81
+ "display": { "$type": "fontFamily", "$value": ["Bricolage Grotesque", "system-ui", "sans-serif"] },
82
+ "body": { "$type": "fontFamily", "$value": ["Hanken Grotesk", "system-ui", "sans-serif"] },
83
+ "mono": { "$type": "fontFamily", "$value": ["JetBrains Mono", "ui-monospace", "monospace"] }
84
+ },
85
+ "size": {
86
+ "display-1": { "$type": "dimension", "$value": "72px" },
87
+ "display-2": { "$type": "dimension", "$value": "48px" },
88
+ "h": { "$type": "dimension", "$value": "28px" },
89
+ "lead": { "$type": "dimension", "$value": "19px" },
90
+ "body": { "$type": "dimension", "$value": "16px" },
91
+ "small": { "$type": "dimension", "$value": "13px" },
92
+ "meta": { "$type": "dimension", "$value": "11px" }
93
+ },
94
+ "weight": {
95
+ "body": { "$type": "fontWeight", "$value": 400 },
96
+ "semibold": { "$type": "fontWeight", "$value": 600 },
97
+ "bold": { "$type": "fontWeight", "$value": 700 }
98
+ }
99
+ },
100
+ "motion": {
101
+ "duration": {
102
+ "fast": { "$type": "duration", "$value": "120ms" },
103
+ "base": { "$type": "duration", "$value": "180ms" },
104
+ "slow": { "$type": "duration", "$value": "320ms" }
105
+ },
106
+ "ease": {
107
+ "out": { "$type": "cubicBezier", "$value": [0.16, 1, 0.3, 1] },
108
+ "in-out": { "$type": "cubicBezier", "$value": [0.65, 0, 0.35, 1] }
109
+ }
110
+ }
111
+ }