@homepages/template-kit 0.2.0 → 0.4.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.
Files changed (165) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/README.md +96 -17
  3. package/dist/asset-modules.d.ts +81 -0
  4. package/dist/base.css +9 -0
  5. package/dist/cli/check/config.js +41 -0
  6. package/dist/cli/check/css.js +131 -0
  7. package/dist/cli/check/diagnostics.js +32 -0
  8. package/dist/cli/check/index.js +87 -0
  9. package/dist/cli/check/loader.js +146 -0
  10. package/dist/cli/check/paths.js +15 -0
  11. package/dist/cli/check/relativize.js +18 -0
  12. package/dist/cli/check/render-invariants.js +24 -0
  13. package/dist/cli/check/resolve-tool.js +38 -0
  14. package/dist/cli/check/section-assets.js +58 -0
  15. package/dist/cli/check/stages/deps.js +337 -0
  16. package/dist/cli/check/stages/lint.js +46 -0
  17. package/dist/cli/check/stages/render.js +101 -0
  18. package/dist/cli/check/stages/size.js +274 -0
  19. package/dist/cli/check/stages/tree.js +206 -0
  20. package/dist/cli/check/stages/typecheck.js +46 -0
  21. package/dist/cli/check/stages/validate/catalog-exhaustiveness.js +18 -0
  22. package/dist/cli/check/stages/validate/facts.js +18 -0
  23. package/dist/cli/check/stages/validate/formatter-contract.js +14 -0
  24. package/dist/cli/check/stages/validate/group-contract.js +21 -0
  25. package/dist/cli/check/stages/validate/index.js +66 -0
  26. package/dist/cli/check/stages/validate/list-scalar-contract.js +22 -0
  27. package/dist/cli/check/stages/validate/nav-contract.js +93 -0
  28. package/dist/cli/check/stages/validate/nested-slot-contract.js +12 -0
  29. package/dist/cli/check/stages/validate/object-list-contract.js +31 -0
  30. package/dist/cli/check/stages/validate/orchestrator.js +241 -0
  31. package/dist/cli/check/stages/validate/produced-by-contract.js +18 -0
  32. package/dist/cli/check/stages/validate/row-contract.js +31 -0
  33. package/dist/cli/check/stages/validate/select-contract.js +13 -0
  34. package/dist/cli/check/stages/validate/source-contract.js +21 -0
  35. package/dist/cli/check/stages/validate/variant-contract.js +15 -0
  36. package/dist/cli/check/workspace.js +86 -0
  37. package/dist/cli/dev/build-css.js +91 -0
  38. package/dist/cli/dev/catalog.js +35 -0
  39. package/dist/cli/dev/compose-template.js +38 -0
  40. package/dist/cli/dev/content-override.js +58 -0
  41. package/dist/cli/dev/discover.js +35 -0
  42. package/dist/cli/dev/fill-state.js +75 -0
  43. package/dist/cli/dev/index.js +32 -0
  44. package/dist/cli/dev/inspect.js +38 -0
  45. package/dist/cli/dev/island-bootstrap.js +38 -0
  46. package/dist/cli/dev/island-map.js +35 -0
  47. package/dist/cli/dev/island-transform.js +31 -0
  48. package/dist/cli/dev/manifest-instances.js +31 -0
  49. package/dist/cli/dev/render-section.js +24 -0
  50. package/dist/cli/dev/screenshot-target.js +24 -0
  51. package/dist/cli/dev/section-page.js +54 -0
  52. package/dist/cli/dev/server.js +480 -0
  53. package/dist/cli/dev/slot-schema.js +12 -0
  54. package/dist/cli/dev/structure-summary.js +118 -0
  55. package/dist/cli/dev/tailwind.js +32 -0
  56. package/dist/cli/dev/vite-server.js +33 -0
  57. package/dist/cli/dev/workspace.js +63 -0
  58. package/dist/cli/link/index.js +74 -0
  59. package/dist/cli/link/overlay.js +59 -0
  60. package/dist/cli/link/watch.js +25 -0
  61. package/dist/cli/new/emit.js +48 -0
  62. package/dist/cli/new/index.js +69 -0
  63. package/dist/cli/new/scaffold/section/_Renderer.tsx +20 -0
  64. package/dist/cli/new/scaffold/section/_fill-spec.ts +18 -0
  65. package/dist/cli/new/scaffold/section/_fixtures.ts +13 -0
  66. package/dist/cli/new/scaffold/section/_schema.ts +24 -0
  67. package/dist/cli/new/scaffold/template/_manifest.json +9 -0
  68. package/dist/cli/new/scaffold/template/sections/hero/ExpandableText.tsx +45 -0
  69. package/dist/cli/new/scaffold/template/sections/hero/Renderer.tsx +40 -0
  70. package/dist/cli/new/scaffold/template/sections/hero/components/Feature.tsx +14 -0
  71. package/dist/cli/new/scaffold/template/sections/hero/fill-spec.ts +29 -0
  72. package/dist/cli/new/scaffold/template/sections/hero/fixtures.ts +41 -0
  73. package/dist/cli/new/scaffold/template/sections/hero/schema.ts +60 -0
  74. package/dist/cli/new/scaffold/template/theme.css +24 -0
  75. package/dist/cli/new/scaffold/template/theme.ts +27 -0
  76. package/dist/cli/new/scaffold-assets.js +20 -0
  77. package/dist/cli/pack/collect.js +44 -0
  78. package/dist/cli/pack/guards.js +57 -0
  79. package/dist/cli/pack/index.js +66 -0
  80. package/dist/cli/pack/manifest.js +15 -0
  81. package/dist/cli/pack/zip.js +86 -0
  82. package/dist/cli/theme/generate.js +43 -0
  83. package/dist/cli/theme/index.js +33 -0
  84. package/dist/cli/theme/load-theme.js +69 -0
  85. package/dist/cli.js +100 -16
  86. package/dist/contracts/fill-treatments.js +47 -0
  87. package/dist/design-system/theme.d.ts +30 -300
  88. package/dist/design-system/theme.js +112 -90
  89. package/dist/dev-client/assets/index-DfEfrp6P.css +1 -0
  90. package/dist/dev-client/assets/index-bpMP0b0W.js +50 -0
  91. package/dist/dev-client/index.html +13 -0
  92. package/dist/eslint/is-client-file.d.ts +4 -0
  93. package/dist/eslint/rules/no-hex.js +78 -6
  94. package/dist/eslint.js +1 -1
  95. package/dist/index.d.ts +3 -2
  96. package/dist/index.js +2 -2
  97. package/dist/islands/detect.d.ts +16 -0
  98. package/dist/islands/detect.js +22 -0
  99. package/dist/islands/discover.d.ts +5 -0
  100. package/dist/islands/discover.js +19 -0
  101. package/dist/islands/esbuild-plugin.d.ts +13 -0
  102. package/dist/islands/esbuild-plugin.js +39 -0
  103. package/dist/islands/wrap.d.ts +11 -0
  104. package/dist/islands/wrap.js +48 -0
  105. package/dist/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +77 -0
  106. package/dist/node_modules/magic-string/dist/magic-string.es.js +939 -0
  107. package/dist/package.js +1 -1
  108. package/dist/primitives/Image.js +1 -1
  109. package/dist/schema/fill-spec.d.ts +80 -590
  110. package/dist/schema/fixture-schema.d.ts +5 -81
  111. package/dist/schema/manifest.d.ts +39 -475
  112. package/dist/schema/resolve-section-ref.js +10 -0
  113. package/dist/schema/rows.js +10 -0
  114. package/dist/schema/section-nav.js +9 -0
  115. package/dist/schema/section-schema.d.ts +51 -437
  116. package/dist/schema/slot-types.d.ts +12 -16
  117. package/dist/ssr.d.ts +31 -0
  118. package/dist/ssr.js +46 -0
  119. package/dist/styles.css +31 -88
  120. package/docs/INDEX.md +12 -3
  121. package/docs/assets.md +128 -0
  122. package/docs/check.md +124 -0
  123. package/docs/dev.md +187 -0
  124. package/docs/eslint.md +18 -8
  125. package/docs/islands.md +5 -2
  126. package/docs/llms.txt +59 -9
  127. package/docs/new.md +27 -0
  128. package/docs/overview.md +57 -0
  129. package/docs/pack.md +46 -0
  130. package/docs/primitives.md +10 -6
  131. package/docs/quickstart.md +102 -0
  132. package/docs/recipes/INDEX.md +27 -0
  133. package/docs/recipes/bind-property-fact.md +58 -0
  134. package/docs/recipes/collection-slot.md +100 -0
  135. package/docs/recipes/fill-spec-decision.md +66 -0
  136. package/docs/recipes/fixture-states.md +68 -0
  137. package/docs/recipes/image-slot-crop.md +98 -0
  138. package/docs/recipes/interactive-island.md +100 -0
  139. package/docs/recipes/organize-section-folder.md +72 -0
  140. package/docs/recipes/prepare-submission.md +55 -0
  141. package/docs/recipes/second-template.md +49 -0
  142. package/docs/recipes/select-slot.md +59 -0
  143. package/docs/recipes/static-asset.md +103 -0
  144. package/docs/recipes/third-party-package.md +90 -0
  145. package/docs/rules/INDEX.md +14 -5
  146. package/docs/rules/bundle-binary-asset.md +83 -0
  147. package/docs/rules/bundle-incomplete.md +2 -2
  148. package/docs/rules/css-reason.md +3 -3
  149. package/docs/rules/fixtures-invalid.md +96 -0
  150. package/docs/rules/license-denied.md +11 -3
  151. package/docs/rules/manifest-invalid.md +99 -0
  152. package/docs/rules/no-bare-css-import.md +8 -8
  153. package/docs/rules/no-css-import-from-render-path.md +7 -8
  154. package/docs/rules/no-hex.md +76 -10
  155. package/docs/rules/no-templates.md +87 -0
  156. package/docs/rules/parse-error.md +76 -0
  157. package/docs/rules/schema-invalid.md +96 -0
  158. package/docs/rules/size-assets.md +88 -0
  159. package/docs/rules/size-island-bundle.md +123 -0
  160. package/docs/rules/size-section-css.md +19 -14
  161. package/docs/schema-system.md +28 -28
  162. package/docs/theme-and-css.md +157 -92
  163. package/docs/vocabulary.md +98 -0
  164. package/package.json +14 -11
  165. package/tsconfig.json +1 -1
@@ -0,0 +1,47 @@
1
+ //#region src/contracts/fill-treatments.ts
2
+ const GENERATED_KINDS = ["text", "list<text>"];
3
+ const COMPUTED_FNS = ["haversine_miles", "mint_id"];
4
+ const CATALOG_FIELDS = [
5
+ "key",
6
+ "label",
7
+ "icon",
8
+ "slot"
9
+ ];
10
+ function parseTreatment(raw) {
11
+ if (raw === "source") return {
12
+ kind: "source",
13
+ path: null
14
+ };
15
+ if (raw.startsWith("source:")) {
16
+ const path = raw.slice(7);
17
+ return path ? {
18
+ kind: "source",
19
+ path
20
+ } : null;
21
+ }
22
+ if (raw.startsWith("computed:")) {
23
+ const fn = raw.slice(9);
24
+ return COMPUTED_FNS.includes(fn) ? {
25
+ kind: "computed",
26
+ fn
27
+ } : null;
28
+ }
29
+ if (raw.startsWith("catalog:")) {
30
+ const field = raw.slice(8);
31
+ return CATALOG_FIELDS.includes(field) ? {
32
+ kind: "catalog",
33
+ field
34
+ } : null;
35
+ }
36
+ if (raw.startsWith("generated:")) {
37
+ const outKind = raw.slice(10);
38
+ return GENERATED_KINDS.includes(outKind) ? {
39
+ kind: "generated",
40
+ outKind
41
+ } : null;
42
+ }
43
+ return null;
44
+ }
45
+
46
+ //#endregion
47
+ export { CATALOG_FIELDS, COMPUTED_FNS, GENERATED_KINDS, parseTreatment };
@@ -1,308 +1,38 @@
1
1
  import { z } from "zod";
2
2
  //#region src/design-system/theme.d.ts
3
- declare const COLOR_ROLE_VARS: {
4
- readonly ink: "--tr-color-ink";
5
- readonly inkSoft: "--tr-color-ink-soft";
6
- readonly inkMute: "--tr-color-ink-mute";
7
- readonly background: "--tr-color-background";
8
- readonly surface: "--tr-color-surface";
9
- readonly surfaceAlt: "--tr-color-surface-alt";
10
- readonly border: "--tr-color-border";
11
- readonly borderStrong: "--tr-color-border-strong";
12
- readonly primary: "--tr-color-primary";
13
- readonly primaryFg: "--tr-color-primary-fg";
14
- readonly primaryStrong: "--tr-color-primary-darker";
15
- readonly accent: "--tr-color-accent";
16
- readonly accentFg: "--tr-color-accent-contrast";
17
- readonly accentStrong: "--tr-color-accent-strong";
18
- readonly overlay: "--tr-color-overlay";
19
- readonly overlayStrong: "--tr-color-overlay-strong";
20
- readonly overlayLight: "--tr-color-overlay-light";
21
- readonly darkBg: "--tr-color-footer-bg";
22
- readonly darkFg: "--tr-color-footer-fg";
23
- readonly darkFgMute: "--tr-color-footer-fg-mute";
24
- readonly darkBorder: "--tr-color-footer-divider";
25
- };
26
- type ColorRole = keyof typeof COLOR_ROLE_VARS;
27
- declare const FONT_ROLE_VARS: {
28
- readonly body: "--tr-font-sans";
29
- readonly heading: "--tr-font-serif";
30
- readonly numeric: "--tr-font-num";
31
- readonly label: "--tr-font-label";
32
- };
33
- declare const TYPE_STEP_VARS: {
34
- readonly xs: "--tr-text-xs";
35
- readonly sm: "--tr-text-sm";
36
- readonly base: "--tr-text-base";
37
- readonly lg: "--tr-text-lg";
38
- readonly xl: "--tr-text-xl";
39
- readonly "2xl": "--tr-text-2xl";
40
- readonly "3xl": "--tr-text-3xl";
41
- readonly "4xl": "--tr-text-4xl";
42
- readonly "5xl": "--tr-text-5xl";
43
- readonly display: "--tr-text-display";
44
- };
45
- declare const RADIUS_VARS: {
46
- readonly sm: "--tr-radius-sm";
47
- readonly md: "--tr-radius-md";
48
- readonly lg: "--tr-radius-lg";
49
- readonly full: "--tr-radius-full";
50
- };
51
- declare const SHADOW_VARS: {
52
- readonly sm: "--tr-shadow-sm";
53
- readonly md: "--tr-shadow-md";
54
- readonly lg: "--tr-shadow-lg";
55
- };
56
- declare const LAYOUT_VARS: {
57
- readonly containerMax: "--tr-container-max";
58
- readonly containerPad: "--tr-container-pad";
59
- readonly headerHeight: "--tr-header-height";
60
- };
3
+ declare const TokenNameSchema: z.ZodString;
61
4
  declare const TokenThemeSchema: z.ZodObject<{
62
- colors: z.ZodObject<Record<"ink" | "inkSoft" | "inkMute" | "background" | "surface" | "surfaceAlt" | "border" | "borderStrong" | "primary" | "primaryFg" | "primaryStrong" | "accent" | "accentFg" | "accentStrong" | "overlay" | "overlayStrong" | "overlayLight" | "darkBg" | "darkFg" | "darkFgMute" | "darkBorder", z.ZodString>, "strip", z.ZodTypeAny, {
63
- ink: string;
64
- inkSoft: string;
65
- inkMute: string;
66
- background: string;
67
- surface: string;
68
- surfaceAlt: string;
69
- border: string;
70
- borderStrong: string;
71
- primary: string;
72
- primaryFg: string;
73
- primaryStrong: string;
74
- accent: string;
75
- accentFg: string;
76
- accentStrong: string;
77
- overlay: string;
78
- overlayStrong: string;
79
- overlayLight: string;
80
- darkBg: string;
81
- darkFg: string;
82
- darkFgMute: string;
83
- darkBorder: string;
84
- }, {
85
- ink: string;
86
- inkSoft: string;
87
- inkMute: string;
88
- background: string;
89
- surface: string;
90
- surfaceAlt: string;
91
- border: string;
92
- borderStrong: string;
93
- primary: string;
94
- primaryFg: string;
95
- primaryStrong: string;
96
- accent: string;
97
- accentFg: string;
98
- accentStrong: string;
99
- overlay: string;
100
- overlayStrong: string;
101
- overlayLight: string;
102
- darkBg: string;
103
- darkFg: string;
104
- darkFgMute: string;
105
- darkBorder: string;
106
- }>;
107
- /** Optional open brand palette → `--tr-color-<name>` (the escape hatch). */
108
- palette: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
109
- fonts: z.ZodObject<Record<"body" | "heading" | "numeric" | "label", z.ZodString> & {
110
- /** Font-loading block (@import / @font-face) emitted before :root. */
111
- fontFaces: z.ZodOptional<z.ZodString>;
112
- }, "strip", z.ZodTypeAny, {
113
- body: string;
114
- heading: string;
115
- numeric: string;
116
- label: string;
117
- fontFaces?: string | undefined;
118
- }, {
119
- body: string;
120
- heading: string;
121
- numeric: string;
122
- label: string;
123
- fontFaces?: string | undefined;
124
- }>;
125
- type: z.ZodObject<Record<"xs" | "sm" | "base" | "lg" | "xl" | "2xl" | "3xl" | "4xl" | "5xl" | "display", z.ZodString>, "strip", z.ZodTypeAny, {
126
- xs: string;
127
- sm: string;
128
- base: string;
129
- lg: string;
130
- xl: string;
131
- "2xl": string;
132
- "3xl": string;
133
- "4xl": string;
134
- "5xl": string;
135
- display: string;
136
- }, {
137
- xs: string;
138
- sm: string;
139
- base: string;
140
- lg: string;
141
- xl: string;
142
- "2xl": string;
143
- "3xl": string;
144
- "4xl": string;
145
- "5xl": string;
146
- display: string;
147
- }>;
148
- radii: z.ZodObject<Record<"sm" | "lg" | "md" | "full", z.ZodString>, "strip", z.ZodTypeAny, {
149
- sm: string;
150
- lg: string;
151
- md: string;
152
- full: string;
153
- }, {
154
- sm: string;
155
- lg: string;
156
- md: string;
157
- full: string;
158
- }>;
159
- shadows: z.ZodObject<Record<"sm" | "lg" | "md", z.ZodString>, "strip", z.ZodTypeAny, {
160
- sm: string;
161
- lg: string;
162
- md: string;
163
- }, {
164
- sm: string;
165
- lg: string;
166
- md: string;
167
- }>;
168
- layout: z.ZodObject<Record<"containerMax" | "containerPad" | "headerHeight", z.ZodString>, "strip", z.ZodTypeAny, {
169
- containerMax: string;
170
- containerPad: string;
171
- headerHeight: string;
172
- }, {
173
- containerMax: string;
174
- containerPad: string;
175
- headerHeight: string;
176
- }>;
177
- }, "strict", z.ZodTypeAny, {
178
- type: {
179
- xs: string;
180
- sm: string;
181
- base: string;
182
- lg: string;
183
- xl: string;
184
- "2xl": string;
185
- "3xl": string;
186
- "4xl": string;
187
- "5xl": string;
188
- display: string;
189
- };
190
- colors: {
191
- ink: string;
192
- inkSoft: string;
193
- inkMute: string;
194
- background: string;
195
- surface: string;
196
- surfaceAlt: string;
197
- border: string;
198
- borderStrong: string;
199
- primary: string;
200
- primaryFg: string;
201
- primaryStrong: string;
202
- accent: string;
203
- accentFg: string;
204
- accentStrong: string;
205
- overlay: string;
206
- overlayStrong: string;
207
- overlayLight: string;
208
- darkBg: string;
209
- darkFg: string;
210
- darkFgMute: string;
211
- darkBorder: string;
212
- };
213
- fonts: {
214
- body: string;
215
- heading: string;
216
- numeric: string;
217
- label: string;
218
- fontFaces?: string | undefined;
219
- };
220
- radii: {
221
- sm: string;
222
- lg: string;
223
- md: string;
224
- full: string;
225
- };
226
- shadows: {
227
- sm: string;
228
- lg: string;
229
- md: string;
230
- };
231
- layout: {
232
- containerMax: string;
233
- containerPad: string;
234
- headerHeight: string;
235
- };
236
- palette?: Record<string, string> | undefined;
237
- }, {
238
- type: {
239
- xs: string;
240
- sm: string;
241
- base: string;
242
- lg: string;
243
- xl: string;
244
- "2xl": string;
245
- "3xl": string;
246
- "4xl": string;
247
- "5xl": string;
248
- display: string;
249
- };
250
- colors: {
251
- ink: string;
252
- inkSoft: string;
253
- inkMute: string;
254
- background: string;
255
- surface: string;
256
- surfaceAlt: string;
257
- border: string;
258
- borderStrong: string;
259
- primary: string;
260
- primaryFg: string;
261
- primaryStrong: string;
262
- accent: string;
263
- accentFg: string;
264
- accentStrong: string;
265
- overlay: string;
266
- overlayStrong: string;
267
- overlayLight: string;
268
- darkBg: string;
269
- darkFg: string;
270
- darkFgMute: string;
271
- darkBorder: string;
272
- };
273
- fonts: {
274
- body: string;
275
- heading: string;
276
- numeric: string;
277
- label: string;
278
- fontFaces?: string | undefined;
279
- };
280
- radii: {
281
- sm: string;
282
- lg: string;
283
- md: string;
284
- full: string;
285
- };
286
- shadows: {
287
- sm: string;
288
- lg: string;
289
- md: string;
290
- };
291
- layout: {
292
- containerMax: string;
293
- containerPad: string;
294
- headerHeight: string;
295
- };
296
- palette?: Record<string, string> | undefined;
297
- }>;
5
+ colors: z.ZodRecord<z.ZodString, z.ZodString>;
6
+ fonts: z.ZodRecord<z.ZodString, z.ZodString>;
7
+ type: z.ZodRecord<z.ZodString, z.ZodString>;
8
+ radii: z.ZodRecord<z.ZodString, z.ZodString>;
9
+ shadows: z.ZodRecord<z.ZodString, z.ZodString>;
10
+ layout: z.ZodRecord<z.ZodString, z.ZodString>;
11
+ fontFaces: z.ZodOptional<z.ZodString>;
12
+ document: z.ZodObject<{
13
+ font: z.ZodString;
14
+ color: z.ZodString;
15
+ background: z.ZodString;
16
+ fontSize: z.ZodString;
17
+ }, z.core.$strict>;
18
+ }, z.core.$strict>;
298
19
  type TokenTheme = z.infer<typeof TokenThemeSchema>;
299
20
  /**
300
- * Compile a `TokenTheme` into the CSS a template's bundle ships: an optional
301
- * font-loading block, a `:root { --tr-*: … }` declaration that defines the
302
- * design tokens, and a `body` block that applies those tokens to the document.
303
- * Deterministic (stable key order; palette keys sorted) so the same theme
304
- * always content-hashes identically.
21
+ * Compile a `TokenTheme` into the CSS a template's bundle ships:
22
+ *
23
+ * 1. the optional font-loading block;
24
+ * 2. `@theme inline { --color-<name>: var(--tr-color-<name>); }` the Tailwind
25
+ * alias layer, which is what brings the token utilities into existence;
26
+ * 3. `:root { --tr-color-<name>: <value>; … }` — the values behind those aliases;
27
+ * 4. `body { … }` — the document defaults, as `var()` indirections so that a runtime
28
+ * `:root` override still cascades to the page.
29
+ *
30
+ * The output MUST be compiled INSIDE the Tailwind build graph: `@theme` is a Tailwind
31
+ * directive, and anywhere else it is inert — silently, yielding a stylesheet with no
32
+ * token utilities and no error to explain why.
33
+ *
34
+ * Deterministic: keys are sorted, so the same theme always content-hashes identically.
305
35
  */
306
36
  declare function compileThemeToCss(theme: TokenTheme): string;
307
37
  //#endregion
308
- export { COLOR_ROLE_VARS, ColorRole, FONT_ROLE_VARS, LAYOUT_VARS, RADIUS_VARS, SHADOW_VARS, TYPE_STEP_VARS, TokenTheme, TokenThemeSchema, compileThemeToCss };
38
+ export { TokenNameSchema, TokenTheme, TokenThemeSchema, compileThemeToCss };
@@ -1,102 +1,124 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  //#region src/design-system/theme.ts
4
- const COLOR_ROLE_VARS = {
5
- ink: "--tr-color-ink",
6
- inkSoft: "--tr-color-ink-soft",
7
- inkMute: "--tr-color-ink-mute",
8
- background: "--tr-color-background",
9
- surface: "--tr-color-surface",
10
- surfaceAlt: "--tr-color-surface-alt",
11
- border: "--tr-color-border",
12
- borderStrong: "--tr-color-border-strong",
13
- primary: "--tr-color-primary",
14
- primaryFg: "--tr-color-primary-fg",
15
- primaryStrong: "--tr-color-primary-darker",
16
- accent: "--tr-color-accent",
17
- accentFg: "--tr-color-accent-contrast",
18
- accentStrong: "--tr-color-accent-strong",
19
- overlay: "--tr-color-overlay",
20
- overlayStrong: "--tr-color-overlay-strong",
21
- overlayLight: "--tr-color-overlay-light",
22
- darkBg: "--tr-color-footer-bg",
23
- darkFg: "--tr-color-footer-fg",
24
- darkFgMute: "--tr-color-footer-fg-mute",
25
- darkBorder: "--tr-color-footer-divider"
4
+ const TokenNameSchema = z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "token names are kebab-case: [a-z0-9] groups separated by single hyphens");
5
+ const tokens = z.record(TokenNameSchema, z.string().min(1));
6
+ const NAMESPACES = {
7
+ colors: {
8
+ tw: "--color-",
9
+ tr: "--tr-color-"
10
+ },
11
+ fonts: {
12
+ tw: "--font-",
13
+ tr: "--tr-font-"
14
+ },
15
+ type: {
16
+ tw: "--text-",
17
+ tr: "--tr-text-"
18
+ },
19
+ radii: {
20
+ tw: "--radius-",
21
+ tr: "--tr-radius-"
22
+ },
23
+ shadows: {
24
+ tw: "--shadow-",
25
+ tr: "--tr-shadow-"
26
+ }
26
27
  };
27
- const FONT_ROLE_VARS = {
28
- body: "--tr-font-sans",
29
- heading: "--tr-font-serif",
30
- numeric: "--tr-font-num",
31
- label: "--tr-font-label"
32
- };
33
- const TYPE_STEP_VARS = {
34
- xs: "--tr-text-xs",
35
- sm: "--tr-text-sm",
36
- base: "--tr-text-base",
37
- lg: "--tr-text-lg",
38
- xl: "--tr-text-xl",
39
- "2xl": "--tr-text-2xl",
40
- "3xl": "--tr-text-3xl",
41
- "4xl": "--tr-text-4xl",
42
- "5xl": "--tr-text-5xl",
43
- display: "--tr-text-display"
44
- };
45
- const RADIUS_VARS = {
46
- sm: "--tr-radius-sm",
47
- md: "--tr-radius-md",
48
- lg: "--tr-radius-lg",
49
- full: "--tr-radius-full"
50
- };
51
- const SHADOW_VARS = {
52
- sm: "--tr-shadow-sm",
53
- md: "--tr-shadow-md",
54
- lg: "--tr-shadow-lg"
55
- };
56
- const LAYOUT_VARS = {
57
- containerMax: "--tr-container-max",
58
- containerPad: "--tr-container-pad",
59
- headerHeight: "--tr-header-height"
60
- };
61
- const colorRoles = Object.fromEntries(Object.keys(COLOR_ROLE_VARS).map((k) => [k, z.string().min(1)]));
62
- const scale = (keys) => z.object(Object.fromEntries(keys.map((k) => [k, z.string().min(1)])));
63
- const TokenThemeSchema = z.object({
64
- colors: z.object(colorRoles),
65
- /** Optional open brand palette → `--tr-color-<name>` (the escape hatch). */
66
- palette: z.record(z.string().min(1)).optional(),
67
- fonts: scale(Object.keys(FONT_ROLE_VARS)).extend({
68
- /** Font-loading block (@import / @font-face) emitted before :root. */
69
- fontFaces: z.string().optional() }),
70
- type: scale(Object.keys(TYPE_STEP_VARS)),
71
- radii: scale(Object.keys(RADIUS_VARS)),
72
- shadows: scale(Object.keys(SHADOW_VARS)),
73
- layout: scale(Object.keys(LAYOUT_VARS))
28
+ const NAMESPACED_GROUPS = Object.keys(NAMESPACES);
29
+ const DocumentDefaultsSchema = z.object({
30
+ font: TokenNameSchema,
31
+ color: TokenNameSchema,
32
+ background: TokenNameSchema,
33
+ fontSize: TokenNameSchema
74
34
  }).strict();
75
- const BODY_DEFAULTS = `body {
76
- font-family: var(--tr-font-sans);
77
- color: var(--tr-color-ink);
78
- background: var(--tr-color-background);
79
- font-size: var(--tr-text-base);
80
- }`;
35
+ const TokenThemeSchema = z.object({
36
+ colors: tokens,
37
+ fonts: tokens,
38
+ type: tokens,
39
+ radii: tokens,
40
+ shadows: tokens,
41
+ /** Layout values → `--tr-<name>`. No Tailwind alias: not a utility namespace. */
42
+ layout: tokens,
43
+ /** Font-loading block (@import / @font-face), emitted before every other block. */
44
+ fontFaces: z.string().optional(),
45
+ document: DocumentDefaultsSchema
46
+ }).strict().superRefine((theme, ctx) => {
47
+ for (const name of Object.keys(theme.colors)) if (Object.hasOwn(theme.type, name)) ctx.addIssue({
48
+ code: z.ZodIssueCode.custom,
49
+ path: ["type", name],
50
+ message: `"${name}" is declared in both \`colors\` and \`type\`, and both compile to \`text-${name}\` — rename one.`
51
+ });
52
+ }).superRefine((theme, ctx) => {
53
+ const refs = [
54
+ [
55
+ "font",
56
+ "fonts",
57
+ theme.document.font
58
+ ],
59
+ [
60
+ "color",
61
+ "colors",
62
+ theme.document.color
63
+ ],
64
+ [
65
+ "background",
66
+ "colors",
67
+ theme.document.background
68
+ ],
69
+ [
70
+ "fontSize",
71
+ "type",
72
+ theme.document.fontSize
73
+ ]
74
+ ];
75
+ for (const [field, group, name] of refs) if (!Object.hasOwn(theme[group], name)) ctx.addIssue({
76
+ code: z.ZodIssueCode.custom,
77
+ path: ["document", field],
78
+ message: `document.${field}: "${name}" is not a declared \`${group}\` token`
79
+ });
80
+ });
81
81
  /**
82
- * Compile a `TokenTheme` into the CSS a template's bundle ships: an optional
83
- * font-loading block, a `:root { --tr-*: … }` declaration that defines the
84
- * design tokens, and a `body` block that applies those tokens to the document.
85
- * Deterministic (stable key order; palette keys sorted) so the same theme
86
- * always content-hashes identically.
82
+ * Compile a `TokenTheme` into the CSS a template's bundle ships:
83
+ *
84
+ * 1. the optional font-loading block;
85
+ * 2. `@theme inline { --color-<name>: var(--tr-color-<name>); }` the Tailwind
86
+ * alias layer, which is what brings the token utilities into existence;
87
+ * 3. `:root { --tr-color-<name>: <value>; … }` — the values behind those aliases;
88
+ * 4. `body { … }` — the document defaults, as `var()` indirections so that a runtime
89
+ * `:root` override still cascades to the page.
90
+ *
91
+ * The output MUST be compiled INSIDE the Tailwind build graph: `@theme` is a Tailwind
92
+ * directive, and anywhere else it is inert — silently, yielding a stylesheet with no
93
+ * token utilities and no error to explain why.
94
+ *
95
+ * Deterministic: keys are sorted, so the same theme always content-hashes identically.
87
96
  */
88
97
  function compileThemeToCss(theme) {
98
+ const aliases = [];
89
99
  const decls = [];
90
- for (const role of Object.keys(COLOR_ROLE_VARS)) decls.push(` ${COLOR_ROLE_VARS[role]}: ${theme.colors[role]};`);
91
- if (theme.palette) for (const name of Object.keys(theme.palette).sort()) decls.push(` --tr-color-${name}: ${theme.palette[name]};`);
92
- for (const k of Object.keys(FONT_ROLE_VARS)) decls.push(` ${FONT_ROLE_VARS[k]}: ${theme.fonts[k]};`);
93
- for (const k of Object.keys(TYPE_STEP_VARS)) decls.push(` ${TYPE_STEP_VARS[k]}: ${theme.type[k]};`);
94
- for (const k of Object.keys(RADIUS_VARS)) decls.push(` ${RADIUS_VARS[k]}: ${theme.radii[k]};`);
95
- for (const k of Object.keys(SHADOW_VARS)) decls.push(` ${SHADOW_VARS[k]}: ${theme.shadows[k]};`);
96
- for (const k of Object.keys(LAYOUT_VARS)) decls.push(` ${LAYOUT_VARS[k]}: ${theme.layout[k]};`);
97
- const blocks = `${`:root {\n${decls.join("\n")}\n}`}\n\n${BODY_DEFAULTS}`;
98
- return theme.fonts.fontFaces ? `${theme.fonts.fontFaces}\n\n${blocks}\n` : `${blocks}\n`;
100
+ for (const group of NAMESPACED_GROUPS) {
101
+ const { tw, tr } = NAMESPACES[group];
102
+ for (const name of Object.keys(theme[group]).sort()) {
103
+ aliases.push(` ${tw}${name}: var(${tr}${name});`);
104
+ decls.push(` ${tr}${name}: ${theme[group][name]};`);
105
+ }
106
+ }
107
+ for (const name of Object.keys(theme.layout).sort()) decls.push(` --tr-${name}: ${theme.layout[name]};`);
108
+ const { font, color, background, fontSize } = theme.document;
109
+ const body = `body {
110
+ font-family: var(--tr-font-${font});
111
+ color: var(--tr-color-${color});
112
+ background: var(--tr-color-${background});
113
+ font-size: var(--tr-text-${fontSize});
114
+ }`;
115
+ const blocks = [
116
+ `@theme inline {\n${aliases.join("\n")}\n}`,
117
+ `:root {\n${decls.join("\n")}\n}`,
118
+ body
119
+ ].join("\n\n");
120
+ return theme.fontFaces ? `${theme.fontFaces}\n\n${blocks}\n` : `${blocks}\n`;
99
121
  }
100
122
 
101
123
  //#endregion
102
- export { COLOR_ROLE_VARS, FONT_ROLE_VARS, LAYOUT_VARS, RADIUS_VARS, SHADOW_VARS, TYPE_STEP_VARS, TokenThemeSchema, compileThemeToCss };
124
+ export { TokenNameSchema, TokenThemeSchema, compileThemeToCss };
@@ -0,0 +1 @@
1
+ *{box-sizing:border-box}html,body,#root{margin:0;padding:0;height:100%;background:#0f1115;color:#e6e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Helvetica,Arial,sans-serif}a{color:inherit;text-decoration:none}button,select,input,textarea{font:inherit;color:inherit;background:#1a1d24;border:1px solid #2c313a;border-radius:4px;padding:6px 10px}button{cursor:pointer}button:hover{background:#232831}button:disabled{opacity:.4;cursor:default}.empty{padding:40px;color:#9ca0a8;text-align:center}.boot-error{padding:24px;color:#ff8b8b;font:13px/1.5 ui-monospace,Menlo,monospace;white-space:pre-wrap}.canvas-mirror{display:flex;flex-direction:column;height:100%;min-height:0}.canvas-toolbar{display:flex;gap:8px;padding:6px 10px;border-bottom:1px solid #e2e5ea;background:#fafbfc}.canvas-toolbar button{font:12px system-ui,sans-serif;padding:3px 8px;border:1px solid #cbd2dc;border-radius:5px;background:#fff;cursor:pointer}.canvas-iframe{flex:1 1 auto;width:100%;border:0;background:#fff;min-height:0}.home{max-width:880px;margin:0 auto;padding:40px 24px}.home-header{margin-bottom:28px}.home-title{font-size:15px;font-weight:600}.home-tagline{font-size:13px;color:#9ca0a8;margin-top:4px}.home-template{display:inline-flex;align-items:center;gap:8px;font-size:12px;color:#9ca0a8;margin-bottom:24px}.home-template select{min-width:160px}.home-tools{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.tool-card{display:flex;flex-direction:column;gap:12px;background:#14171c;border:1px solid #21252d;border-radius:8px;padding:20px}.tool-card h2{font-size:14px;font-weight:600;margin:0}.tool-card p{font-size:13px;color:#9ca0a8;margin:0;line-height:1.5}.tool-card .controls{display:flex;flex-direction:column;gap:8px}.tool-card label{display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:12px;color:#9ca0a8}.tool-card label select{min-width:140px}.open-btn{margin-top:auto;text-align:center;padding:8px 12px;border:1px solid #2c313a;border-radius:6px;background:#1a1d24;color:inherit;cursor:pointer}.open-btn:hover{background:#232831}.open-btn:disabled{opacity:.4;cursor:default}.home-hint{font-size:12px;color:#6b7280}.home-link{font-weight:600;color:#1a1d24}.canvas-toolbar .spacer{flex:1}.canvas-mirror__stage{display:flex;min-height:0;flex:1}.canvas-mirror__stage .canvas-iframe{flex:1}.instance-panel{width:340px;flex:none;overflow-y:auto;border-left:1px solid #e5e7eb;padding:12px;background:#fff;font:13px system-ui,sans-serif;display:flex;flex-direction:column;gap:10px}.instance-panel__head{display:flex;flex-direction:column;gap:8px}.instance-panel__slot{display:flex;align-items:center;gap:8px}.instance-panel__type{color:#6b7280}.instance-panel__fill{margin-left:auto;padding:1px 7px;border-radius:999px;font-size:11px}.instance-panel__fill--filled{background:#dcfce7;color:#166534}.instance-panel__fill--empty{background:#f3f4f6;color:#6b7280}.instance-panel__fill--missing{background:#fee2e2;color:#991b1b}.instance-panel__fixture{display:flex;flex-direction:column;gap:3px;color:#6b7280}.instance-panel__json{width:100%;min-height:180px;font:12px ui-monospace,monospace;resize:vertical}.instance-panel__error{color:#b91c1c;white-space:pre-wrap}.instance-panel__actions{display:flex;gap:8px}.instance-panel__note{color:#9ca3af;font-size:11px}