@homepages/template-kit 0.0.1 → 0.1.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 (90) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/README.md +67 -11
  3. package/dist/base.css +85 -0
  4. package/dist/browser.d.ts +26 -0
  5. package/dist/browser.js +55 -0
  6. package/dist/cli.js +1 -1
  7. package/dist/contracts/image-descriptor.d.ts +17 -0
  8. package/dist/contracts/markers.d.ts +20 -0
  9. package/dist/contracts/markers.js +21 -0
  10. package/dist/contracts/slot-catalog.d.ts +237 -0
  11. package/dist/contracts/slot-catalog.js +247 -0
  12. package/dist/contracts/transforms.d.ts +108 -0
  13. package/dist/contracts/transforms.js +117 -0
  14. package/dist/design-system/breakpoints.d.ts +5 -0
  15. package/dist/design-system/breakpoints.js +14 -0
  16. package/dist/design-system/theme.d.ts +307 -0
  17. package/dist/design-system/theme.js +95 -0
  18. package/dist/eslint/is-client-file.js +79 -0
  19. package/dist/eslint/rules/image-bare-needs-reason.js +38 -0
  20. package/dist/eslint/rules/no-client-directive-in-contract.js +26 -0
  21. package/dist/eslint/rules/no-client-runtime-in-server.js +99 -0
  22. package/dist/eslint/rules/no-css-import-from-render-path.js +23 -0
  23. package/dist/eslint/rules/no-hex.js +111 -0
  24. package/dist/eslint/rules/no-inline-style.js +23 -0
  25. package/dist/eslint/rules/no-nondeterminism.js +44 -0
  26. package/dist/eslint/rules/no-raw-element.js +32 -0
  27. package/dist/eslint/rules/props-from-schema.js +32 -0
  28. package/dist/eslint/rules/serializable-island-props.js +108 -0
  29. package/dist/eslint/rules/slot-marker-literal.js +33 -0
  30. package/dist/eslint.d.ts +4 -8
  31. package/dist/eslint.js +75 -11
  32. package/dist/fixtures/sample-property.d.ts +67 -0
  33. package/dist/fixtures/sample-property.js +707 -0
  34. package/dist/fixtures.d.ts +2 -0
  35. package/dist/fixtures.js +3 -0
  36. package/dist/index.d.ts +28 -12
  37. package/dist/index.js +34 -2
  38. package/dist/island-runtime.d.ts +30 -0
  39. package/dist/island-runtime.js +73 -0
  40. package/dist/islands/contract.d.ts +56 -0
  41. package/dist/islands/contract.js +71 -0
  42. package/dist/islands/marker.d.ts +16 -0
  43. package/dist/islands/marker.js +18 -0
  44. package/dist/package.js +5 -0
  45. package/dist/primitives/Image.d.ts +49 -0
  46. package/dist/primitives/Image.js +95 -0
  47. package/dist/primitives/Section.d.ts +22 -0
  48. package/dist/primitives/Section.js +26 -0
  49. package/dist/primitives/Slot.d.ts +50 -0
  50. package/dist/primitives/Slot.js +39 -0
  51. package/dist/primitives/SlotGroup.d.ts +38 -0
  52. package/dist/primitives/SlotGroup.js +24 -0
  53. package/dist/primitives/SlotItem.d.ts +43 -0
  54. package/dist/primitives/SlotItem.js +38 -0
  55. package/dist/primitives/index.d.ts +7 -0
  56. package/dist/primitives/picture-sources.d.ts +22 -0
  57. package/dist/primitives/picture-sources.js +52 -0
  58. package/dist/schema/fill-spec.d.ts +692 -0
  59. package/dist/schema/fill-spec.js +98 -0
  60. package/dist/schema/fixture-schema.d.ts +143 -0
  61. package/dist/schema/fixture-schema.js +113 -0
  62. package/dist/schema/index.d.ts +16 -0
  63. package/dist/schema/manifest.d.ts +527 -0
  64. package/dist/schema/manifest.js +73 -0
  65. package/dist/schema/property-facts.d.ts +44 -0
  66. package/dist/schema/resolve-variants.d.ts +11 -0
  67. package/dist/schema/resolve-variants.js +15 -0
  68. package/dist/schema/runtime-values.d.ts +45 -0
  69. package/dist/schema/section-nav.d.ts +7 -0
  70. package/dist/schema/section-props.d.ts +69 -0
  71. package/dist/schema/section-schema.d.ts +533 -0
  72. package/dist/schema/section-schema.js +72 -0
  73. package/dist/schema/slot-types.d.ts +117 -0
  74. package/dist/schema/slot-types.js +181 -0
  75. package/dist/schema/source.d.ts +13 -0
  76. package/dist/schema/source.js +12 -0
  77. package/dist/schema/synthesize-nullable.d.ts +18 -0
  78. package/dist/schema/synthesize-nullable.js +47 -0
  79. package/dist/styles.css +131 -9
  80. package/docs/INDEX.md +6 -4
  81. package/docs/eslint.md +90 -0
  82. package/docs/islands.md +150 -0
  83. package/docs/llms.txt +30 -3
  84. package/docs/primitives.md +214 -0
  85. package/docs/schema-system.md +240 -0
  86. package/docs/theme-and-css.md +179 -0
  87. package/package.json +30 -6
  88. package/tsconfig.base.json +16 -0
  89. package/tsconfig.json +2 -13
  90. package/dist/src-dZr5N30y.js +0 -35
@@ -0,0 +1,72 @@
1
+ import { CONTENT_SLOT_TYPES, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES } from "../contracts/slot-catalog.js";
2
+ import { ContentSlot, SlotDef, defaultForSlotType } from "./slot-types.js";
3
+ import { derived, direct } from "./source.js";
4
+ import { z } from "zod";
5
+
6
+ //#region src/schema/section-schema.ts
7
+ const optionAuthorityShape = {
8
+ user_editable: z.boolean().default(false),
9
+ template_overridable: z.boolean().default(false),
10
+ planner_settable: z.boolean().default(false),
11
+ affects_decisions: z.array(z.string()).default([]),
12
+ affects_slots: z.array(z.string()).default([])
13
+ };
14
+ const OptionEnum = z.object({
15
+ type: z.literal("enum"),
16
+ values: z.array(z.string()).min(1),
17
+ default: z.string(),
18
+ ...optionAuthorityShape
19
+ }).strict();
20
+ const OptionBool = z.object({
21
+ type: z.literal("bool"),
22
+ default: z.boolean(),
23
+ ...optionAuthorityShape
24
+ }).strict();
25
+ const OptionInt = z.object({
26
+ type: z.literal("int"),
27
+ default: z.number().int(),
28
+ min: z.number().int().optional(),
29
+ max: z.number().int().optional(),
30
+ ...optionAuthorityShape
31
+ }).strict();
32
+ const OptionString = z.object({
33
+ type: z.literal("string"),
34
+ default: z.string(),
35
+ ...optionAuthorityShape
36
+ }).strict();
37
+ const OptionJson = z.object({
38
+ type: z.literal("json"),
39
+ default: z.unknown(),
40
+ ...optionAuthorityShape
41
+ }).strict();
42
+ const OptionDef = z.discriminatedUnion("type", [
43
+ OptionEnum,
44
+ OptionBool,
45
+ OptionInt,
46
+ OptionString,
47
+ OptionJson
48
+ ]);
49
+ const SlotGroupDef = z.object({
50
+ id: z.string(),
51
+ label: z.string(),
52
+ members: z.array(z.union([z.string(), z.tuple([z.string(), z.string()])])).min(1)
53
+ }).strict();
54
+ const SectionMeta = z.object({
55
+ displayName: z.string().min(1),
56
+ anchor: z.string().min(1).optional(),
57
+ groups: z.array(SlotGroupDef).optional(),
58
+ variants: z.record(z.string().min(1), z.object({
59
+ cases: z.array(z.string().min(1)).min(1),
60
+ default: z.string().min(1),
61
+ select: z.custom((v) => typeof v === "function", { message: "variant axis `select` must be a function" })
62
+ }).strict()).optional()
63
+ }).strict();
64
+ const SectionSchema = z.object({
65
+ meta: SectionMeta,
66
+ slots: z.record(z.string(), SlotDef),
67
+ options: z.record(z.string(), OptionDef).default({}),
68
+ slot_order: z.array(z.string()).optional()
69
+ }).strict();
70
+
71
+ //#endregion
72
+ export { CONTENT_SLOT_TYPES, ContentSlot, OptionDef, REFERRAL_FIELDS, REF_SLOT_TYPES, SLOT_TYPES, SectionMeta, SectionSchema, SlotDef, SlotGroupDef, defaultForSlotType, derived, direct };
@@ -0,0 +1,117 @@
1
+ import { SlotType } from "../contracts/slot-catalog.js";
2
+ import { z } from "zod";
3
+ //#region src/schema/slot-types.d.ts
4
+ type SlotTypeDescriptor = {
5
+ shape: z.ZodRawShape;
6
+ default: () => unknown;
7
+ };
8
+ declare const SLOT_TYPE_DESCRIPTORS: Record<SlotType, SlotTypeDescriptor>;
9
+ declare const SlotDef: z.ZodType<Slot>;
10
+ declare function slotDefTypes(): SlotType[];
11
+ declare const ContentSlot: z.ZodObject<{
12
+ type: z.ZodEnum<["text", "number", "select", "image", "image_collection", "list", "object_list", "poi_list", "url"]>;
13
+ editable_by_user: z.ZodDefault<z.ZodBoolean>;
14
+ required: z.ZodDefault<z.ZodBoolean>;
15
+ produced_by: z.ZodOptional<z.ZodString>;
16
+ min: z.ZodOptional<z.ZodNumber>;
17
+ max: z.ZodOptional<z.ZodNumber>;
18
+ }, "strict", z.ZodTypeAny, {
19
+ type: "number" | "select" | "text" | "image" | "image_collection" | "list" | "object_list" | "poi_list" | "url";
20
+ required: boolean;
21
+ editable_by_user: boolean;
22
+ min?: number | undefined;
23
+ max?: number | undefined;
24
+ produced_by?: string | undefined;
25
+ }, {
26
+ type: "number" | "select" | "text" | "image" | "image_collection" | "list" | "object_list" | "poi_list" | "url";
27
+ min?: number | undefined;
28
+ max?: number | undefined;
29
+ required?: boolean | undefined;
30
+ editable_by_user?: boolean | undefined;
31
+ produced_by?: string | undefined;
32
+ }>;
33
+ type SlotBase = {
34
+ required?: boolean;
35
+ editable_by_user?: boolean;
36
+ produced_by?: string;
37
+ source?: {
38
+ kind: string;
39
+ field?: string;
40
+ transform?: string;
41
+ };
42
+ display_name?: string;
43
+ placeholder?: string;
44
+ variant?: {
45
+ axis: string;
46
+ cases: readonly string[];
47
+ };
48
+ };
49
+ type TextSlot = SlotBase & {
50
+ type: "text";
51
+ size?: string;
52
+ format?: unknown;
53
+ length_cap?: number;
54
+ };
55
+ type NumberSlot = SlotBase & {
56
+ type: "number";
57
+ prefix?: string;
58
+ suffix?: string;
59
+ thousands_sep?: boolean;
60
+ decimals?: boolean;
61
+ max_digits?: number;
62
+ format?: unknown;
63
+ };
64
+ type SelectSlot = SlotBase & {
65
+ type: "select";
66
+ values?: string[];
67
+ options?: {
68
+ source: string;
69
+ };
70
+ catalog?: unknown;
71
+ creatable?: boolean;
72
+ fallback_icon?: string;
73
+ assigns?: Record<string, string>;
74
+ };
75
+ type ImageSlot = SlotBase & {
76
+ type: "image";
77
+ min?: number;
78
+ max?: number;
79
+ source_filter?: string;
80
+ recrop?: boolean;
81
+ crop?: unknown;
82
+ frame?: unknown;
83
+ };
84
+ type ImageCollectionSlot = SlotBase & {
85
+ type: "image_collection";
86
+ min?: number;
87
+ max?: number;
88
+ crop?: unknown;
89
+ frame?: unknown;
90
+ };
91
+ type ListSlot = SlotBase & {
92
+ type: "list";
93
+ min?: number;
94
+ max?: number;
95
+ element?: Slot;
96
+ };
97
+ type PoiListSlot = SlotBase & {
98
+ type: "poi_list";
99
+ min?: number;
100
+ max?: number;
101
+ };
102
+ type UrlSlot = SlotBase & {
103
+ type: "url";
104
+ };
105
+ type ObjectListSlot = SlotBase & {
106
+ type: "object_list";
107
+ min?: number;
108
+ max?: number;
109
+ item?: Record<string, Slot>;
110
+ source_title_field?: string;
111
+ item_order?: string[];
112
+ item_layout?: (string | [string, string])[];
113
+ };
114
+ type Slot = TextSlot | NumberSlot | SelectSlot | ImageSlot | ImageCollectionSlot | ListSlot | PoiListSlot | UrlSlot | ObjectListSlot;
115
+ declare function defaultForSlotType(type: string): unknown;
116
+ //#endregion
117
+ export { ContentSlot, SLOT_TYPE_DESCRIPTORS, Slot, SlotDef, defaultForSlotType, slotDefTypes };
@@ -0,0 +1,181 @@
1
+ import { CONTENT_SLOT_TYPES, SLOT_TYPES } from "../contracts/slot-catalog.js";
2
+ import { z } from "zod";
3
+
4
+ //#region src/schema/slot-types.ts
5
+ const slotHeadShape = {
6
+ editable_by_user: z.boolean().default(false),
7
+ required: z.boolean().default(false),
8
+ produced_by: z.string().optional()
9
+ };
10
+ const cropConfigShape = { crop: z.object({
11
+ mode: z.enum(["locked", "free"]),
12
+ aspect: z.number().positive().optional(),
13
+ aspectLabel: z.string().optional()
14
+ }).strict().refine((c) => c.mode !== "locked" || c.aspect != null, { message: "locked crop requires an aspect" }).optional() };
15
+ const frameConfigShape = { frame: z.object({
16
+ breakpoint: z.union([
17
+ z.literal(350),
18
+ z.literal(390),
19
+ z.literal(425),
20
+ z.literal(512),
21
+ z.literal(768),
22
+ z.literal(1024),
23
+ z.literal(1440),
24
+ z.literal(1920)
25
+ ]),
26
+ desktop: z.object({
27
+ aspect: z.number().positive(),
28
+ nominal: z.boolean().optional()
29
+ }).strict(),
30
+ mobile: z.object({
31
+ aspect: z.number().positive(),
32
+ nominal: z.boolean().optional()
33
+ }).strict().optional()
34
+ }).strict().optional() };
35
+ const slotMetadataShape = {
36
+ display_name: z.string().optional(),
37
+ placeholder: z.string().optional(),
38
+ variant: z.object({
39
+ axis: z.string().min(1),
40
+ cases: z.array(z.string().min(1)).min(1)
41
+ }).strict().optional(),
42
+ source: z.discriminatedUnion("kind", [
43
+ z.object({ kind: z.literal("authored") }),
44
+ z.object({
45
+ kind: z.literal("direct"),
46
+ field: z.string()
47
+ }).strict(),
48
+ z.object({
49
+ kind: z.literal("derived"),
50
+ transform: z.string()
51
+ }).strict()
52
+ ]).optional()
53
+ };
54
+ const FormatRef = z.union([z.string(), z.object({ fn: z.string() }).passthrough()]);
55
+ const SLOT_TYPE_DESCRIPTORS = {
56
+ text: {
57
+ shape: {
58
+ size: z.enum([
59
+ "short",
60
+ "medium",
61
+ "long"
62
+ ]).optional(),
63
+ format: FormatRef.optional(),
64
+ length_cap: z.number().int().positive().optional()
65
+ },
66
+ default: () => ""
67
+ },
68
+ number: {
69
+ shape: {
70
+ prefix: z.string().optional(),
71
+ suffix: z.string().optional(),
72
+ thousands_sep: z.boolean().optional(),
73
+ decimals: z.boolean().optional(),
74
+ max_digits: z.number().int().positive().optional(),
75
+ format: FormatRef.optional()
76
+ },
77
+ default: () => null
78
+ },
79
+ select: {
80
+ shape: {
81
+ values: z.array(z.string()).optional(),
82
+ options: z.object({ source: z.string() }).strict().optional(),
83
+ catalog: z.unknown().optional(),
84
+ creatable: z.boolean().optional(),
85
+ fallback_icon: z.string().optional(),
86
+ assigns: z.record(z.string(), z.string()).optional()
87
+ },
88
+ default: () => null
89
+ },
90
+ image: {
91
+ shape: {
92
+ min: z.number().int().nonnegative().optional(),
93
+ max: z.number().int().positive().optional(),
94
+ source_filter: z.string().optional(),
95
+ recrop: z.boolean().optional(),
96
+ ...cropConfigShape,
97
+ ...frameConfigShape
98
+ },
99
+ default: () => ({
100
+ url: "",
101
+ alt: ""
102
+ })
103
+ },
104
+ image_collection: {
105
+ shape: {
106
+ min: z.number().int().nonnegative().optional(),
107
+ max: z.number().int().positive().optional(),
108
+ ...cropConfigShape,
109
+ ...frameConfigShape
110
+ },
111
+ default: () => []
112
+ },
113
+ list: {
114
+ shape: {
115
+ min: z.number().int().nonnegative().optional(),
116
+ max: z.number().int().positive().optional(),
117
+ element: z.lazy(() => SlotDef).optional()
118
+ },
119
+ default: () => []
120
+ },
121
+ poi_list: {
122
+ shape: {
123
+ min: z.number().int().nonnegative().optional(),
124
+ max: z.number().int().positive().optional()
125
+ },
126
+ default: () => []
127
+ },
128
+ url: {
129
+ shape: {},
130
+ default: () => ""
131
+ },
132
+ object_list: {
133
+ shape: {
134
+ min: z.number().int().nonnegative().optional(),
135
+ max: z.number().int().positive().optional(),
136
+ item: z.record(z.string(), z.lazy(() => SlotDef)).optional(),
137
+ source_title_field: z.string().optional(),
138
+ item_order: z.array(z.string()).optional(),
139
+ item_layout: z.array(z.union([z.string(), z.tuple([z.string(), z.string()])])).optional()
140
+ },
141
+ default: () => []
142
+ }
143
+ };
144
+ function slotMember(type, shape) {
145
+ return z.object({
146
+ type: z.literal(type),
147
+ ...slotHeadShape,
148
+ ...shape,
149
+ ...slotMetadataShape
150
+ }).strict();
151
+ }
152
+ const SLOT_DEF_MEMBERS = [
153
+ slotMember("text", SLOT_TYPE_DESCRIPTORS.text.shape),
154
+ slotMember("number", SLOT_TYPE_DESCRIPTORS.number.shape),
155
+ slotMember("select", SLOT_TYPE_DESCRIPTORS.select.shape),
156
+ slotMember("image", SLOT_TYPE_DESCRIPTORS.image.shape),
157
+ slotMember("image_collection", SLOT_TYPE_DESCRIPTORS.image_collection.shape),
158
+ slotMember("list", SLOT_TYPE_DESCRIPTORS.list.shape),
159
+ slotMember("poi_list", SLOT_TYPE_DESCRIPTORS.poi_list.shape),
160
+ slotMember("url", SLOT_TYPE_DESCRIPTORS.url.shape),
161
+ slotMember("object_list", SLOT_TYPE_DESCRIPTORS.object_list.shape)
162
+ ];
163
+ const SlotDef = z.discriminatedUnion("type", [...SLOT_DEF_MEMBERS]);
164
+ function slotDefTypes() {
165
+ return SLOT_DEF_MEMBERS.map((m) => m.shape.type.value);
166
+ }
167
+ const ContentSlot = z.object({
168
+ type: z.enum(CONTENT_SLOT_TYPES),
169
+ editable_by_user: z.boolean().default(false),
170
+ required: z.boolean().default(false),
171
+ produced_by: z.string().optional(),
172
+ min: z.number().int().nonnegative().optional(),
173
+ max: z.number().int().positive().optional()
174
+ }).strict();
175
+ function defaultForSlotType(type) {
176
+ if (!SLOT_TYPES.includes(type)) return null;
177
+ return SLOT_TYPE_DESCRIPTORS[type].default();
178
+ }
179
+
180
+ //#endregion
181
+ export { ContentSlot, SLOT_TYPE_DESCRIPTORS, SlotDef, defaultForSlotType, slotDefTypes };
@@ -0,0 +1,13 @@
1
+ import { SourceFieldName } from "../contracts/slot-catalog.js";
2
+ import { TransformName } from "../contracts/transforms.js";
3
+ //#region src/schema/source.d.ts
4
+ declare const direct: (field: SourceFieldName) => {
5
+ kind: "direct";
6
+ field: SourceFieldName;
7
+ };
8
+ declare const derived: (transform: TransformName) => {
9
+ kind: "derived";
10
+ transform: TransformName;
11
+ };
12
+ //#endregion
13
+ export { derived, direct };
@@ -0,0 +1,12 @@
1
+ //#region src/schema/source.ts
2
+ const direct = (field) => ({
3
+ kind: "direct",
4
+ field
5
+ });
6
+ const derived = (transform) => ({
7
+ kind: "derived",
8
+ transform
9
+ });
10
+
11
+ //#endregion
12
+ export { derived, direct };
@@ -0,0 +1,18 @@
1
+ import "./fixture-schema.js";
2
+ //#region src/schema/synthesize-nullable.d.ts
3
+ type VariantAxes = Record<string, {
4
+ cases: string[];
5
+ default: string;
6
+ }>;
7
+ type SchemaForSynth = {
8
+ slots: Record<string, {
9
+ type: string;
10
+ required?: boolean;
11
+ }>;
12
+ meta?: {
13
+ variants?: VariantAxes | undefined;
14
+ } | undefined;
15
+ variants?: VariantAxes | undefined;
16
+ };
17
+ //#endregion
18
+ export { SchemaForSynth };
@@ -0,0 +1,47 @@
1
+ import { resolveBaseVariants } from "./fixture-schema.js";
2
+
3
+ //#region src/schema/synthesize-nullable.ts
4
+ const LIST_TYPES = /* @__PURE__ */ new Set([
5
+ "list",
6
+ "poi_list",
7
+ "image_collection",
8
+ "object_list"
9
+ ]);
10
+ function emptyValueFor(type) {
11
+ return LIST_TYPES.has(type) ? [] : null;
12
+ }
13
+ function isAlreadyEmpty(value, type) {
14
+ return LIST_TYPES.has(type) ? Array.isArray(value) && value.length === 0 : value === null || value === void 0;
15
+ }
16
+ function synthesizeNullableFixtures(schema, module, selfAnchor) {
17
+ const baseSlots = module.base.slots;
18
+ const baseOptions = module.base.options ?? {};
19
+ const baseAnchors = module.base.anchors ?? {};
20
+ const baseVariants = resolveBaseVariants(schema, module);
21
+ const out = [];
22
+ for (const [slotKey, slotDef] of Object.entries(schema.slots)) {
23
+ if (slotDef.required) continue;
24
+ if (isAlreadyEmpty(baseSlots[slotKey], slotDef.type)) continue;
25
+ const empty = emptyValueFor(slotDef.type);
26
+ out.push({
27
+ id: `null_${slotKey}`,
28
+ label: `${slotKey} = ${Array.isArray(empty) ? "empty" : "null"}`,
29
+ props: {
30
+ slots: {
31
+ ...baseSlots,
32
+ [slotKey]: empty
33
+ },
34
+ options: { ...baseOptions },
35
+ nav: {
36
+ selfAnchor,
37
+ anchors: { ...baseAnchors }
38
+ },
39
+ variants: { ...baseVariants }
40
+ }
41
+ });
42
+ }
43
+ return out;
44
+ }
45
+
46
+ //#endregion
47
+ export { synthesizeNullableFixtures };
package/dist/styles.css CHANGED
@@ -1,16 +1,138 @@
1
1
  /*
2
2
  * @homepages/template-kit — theme tokens + kit source registration.
3
3
  *
4
- * Consumers import this AFTER `@import "tailwindcss";` in their own entry.
5
- * This file must NOT import Tailwind itself: two imports in one graph
6
- * double-emit preflight.
4
+ * Import this AFTER `@import "tailwindcss";` in your own CSS entry:
7
5
  *
8
- * The @source below is resolved relative to THIS FILE'S SHIPPED LOCATION
9
- * (dist/styles.css), registering the kit's compiled JS for class scanning.
10
- * It also overrides Tailwind's default node_modules exclusion.
6
+ * @import "tailwindcss";
7
+ * @import "@homepages/template-kit/styles.css";
8
+ *
9
+ * This file must NOT import Tailwind itself. Exactly one `@import "tailwindcss"` may
10
+ * exist per build graph — your entry owns it. Two double-emit Tailwind's preflight
11
+ * reset (box-sizing declared twice, ~4 KB of duplicate base layer).
12
+ *
13
+ * The @source below resolves relative to THIS FILE'S SHIPPED LOCATION (dist/styles.css),
14
+ * registering the kit's compiled JS for class scanning — that is how the utility classes
15
+ * baked into the kit's primitives reach your compiled CSS. It also overrides Tailwind's
16
+ * default exclusion of node_modules.
17
+ *
18
+ * It is scoped to `primitives/` because those are the ONLY shipped modules that emit
19
+ * markup. Tailwind's scanner is a plain candidate-extractor — it cannot tell code from a
20
+ * comment — and the kit ships unminified (deliberately: it keeps these class-name string
21
+ * literals scannable). Pointed at all of dist/, it therefore compiled prose out of the
22
+ * CLI's and the ESLint preset's doc comments into every consumer's stylesheet: 8 dead
23
+ * utilities and 19 stray @property registrations, ~2.5 KB. If a primitive ever moves out
24
+ * of primitives/, widen this — an unstyled <Image> in every template is the failure mode,
25
+ * and scripts/verify-consumer.mjs is what catches it.
26
+ *
27
+ * The @theme block aliases Tailwind's utility namespace onto the `--tr-*` custom
28
+ * properties your template's theme.ts supplies (`text-ink` → `var(--tr-color-ink)`).
29
+ * The aliases are the cross-template contract; the values behind them are yours.
11
30
  */
12
- @source "./**/*.js";
31
+ @source "./primitives/**/*.js";
32
+
33
+ @theme inline {
34
+ /* ---------- Colors (alias to the --tr-color-* values a theme.ts supplies) ---------- */
35
+ --color-ink: var(--tr-color-ink);
36
+ --color-ink-soft: var(--tr-color-ink-soft);
37
+ --color-ink-mute: var(--tr-color-ink-mute);
38
+ --color-background: var(--tr-color-background);
39
+ --color-surface: var(--tr-color-surface);
40
+ --color-surface-alt: var(--tr-color-surface-alt);
41
+ --color-border: var(--tr-color-border);
42
+ --color-border-strong: var(--tr-color-border-strong);
43
+ --color-accent: var(--tr-color-accent);
44
+ --color-accent-strong: var(--tr-color-accent-strong);
45
+ --color-accent-contrast: var(--tr-color-accent-contrast);
46
+ --color-primary: var(--tr-color-primary);
47
+ --color-primary-fg: var(--tr-color-primary-fg);
48
+ --color-primary-darker: var(--tr-color-primary-darker);
49
+ --color-overlay: var(--tr-color-overlay);
50
+ --color-overlay-strong: var(--tr-color-overlay-strong);
51
+ --color-overlay-light: var(--tr-color-overlay-light);
52
+ --color-footer-bg: var(--tr-color-footer-bg);
53
+ --color-footer-fg: var(--tr-color-footer-fg);
54
+ --color-footer-fg-mute: var(--tr-color-footer-fg-mute);
55
+ --color-footer-divider: var(--tr-color-footer-divider);
56
+
57
+ /* Decorative brand swatches — the open `palette` escape hatch in TokenTheme.
58
+ * A theme that omits one leaves its utility resolving to an unset custom
59
+ * property; only reach for these when a semantic role genuinely doesn't fit. */
60
+ --color-yellow-deep: var(--tr-color-yellow-deep);
61
+ --color-yellow-200: var(--tr-color-yellow-200);
62
+ --color-yellow-100: var(--tr-color-yellow-100);
63
+ --color-brown: var(--tr-color-brown);
64
+ --color-green: var(--tr-color-green);
65
+ --color-blue: var(--tr-color-blue);
66
+ --color-rule: var(--tr-color-rule);
67
+ --color-gray-light: var(--tr-color-gray-light);
68
+ --color-team-gold: var(--tr-color-team-gold);
69
+ --color-mute: var(--tr-color-mute);
70
+ --color-scrim: var(--tr-color-scrim);
71
+ --color-footer-line: var(--tr-color-footer-line);
72
+ --color-footer-mute: var(--tr-color-footer-mute);
73
+ --color-divider-40: var(--tr-color-divider-40);
74
+ --color-contact-deep: var(--tr-color-contact-deep);
75
+ --color-header-deep: var(--tr-color-header-deep);
76
+
77
+ /* ---------- Fonts ---------- */
78
+ --font-sans: var(--tr-font-sans);
79
+ --font-display: var(--tr-font-sans);
80
+ --font-serif: var(--tr-font-serif);
81
+ --font-num: var(--tr-font-num);
82
+ --font-label: var(--tr-font-label);
83
+
84
+ /* ---------- Type scale (mobile baseline; layer up with md:/lg:) ---------- */
85
+ --text-xs: var(--tr-text-xs);
86
+ --text-sm: var(--tr-text-sm);
87
+ --text-base: var(--tr-text-base);
88
+ --text-lg: var(--tr-text-lg);
89
+ --text-xl: var(--tr-text-xl);
90
+ --text-2xl: var(--tr-text-2xl);
91
+ --text-3xl: var(--tr-text-3xl);
92
+ --text-4xl: var(--tr-text-4xl);
93
+ --text-5xl: var(--tr-text-5xl);
94
+ --text-display: var(--tr-text-display);
95
+
96
+ /* ---------- Spacing scale (alias to --tr-space-*) ---------- */
97
+ --spacing: 0.25rem; /* v4 base unit; Tailwind multiplies (e.g. p-4 = 1rem) */
98
+
99
+ /* ---------- Radii ---------- */
100
+ --radius-sm: var(--tr-radius-sm);
101
+ --radius-md: var(--tr-radius-md);
102
+ --radius-lg: var(--tr-radius-lg);
103
+ --radius-full: var(--tr-radius-full);
104
+
105
+ /* ---------- Shadows ---------- */
106
+ --shadow-sm: var(--tr-shadow-sm);
107
+ --shadow-md: var(--tr-shadow-md);
108
+ --shadow-lg: var(--tr-shadow-lg);
109
+
110
+ /* ---------- Motion ---------- */
111
+ --default-transition-duration: var(--tr-duration-fast);
112
+ --default-transition-timing-function: var(--tr-ease);
13
113
 
14
- @theme {
15
- --color-kit-smoke: oklch(0.55 0.2 275);
114
+ /* ---------- Canonical breakpoint ladder ----------
115
+ * Eight canonical widths — the same set the preview grid and the screenshot
116
+ * harness verify a section against (VERIFICATION_WIDTHS is the matching TS
117
+ * constant). 350 is the mobile-first BASE (no prefix); the other seven are
118
+ * named breakpoints:
119
+ *
120
+ * bp390 (390) · bp425 (425) · bp512 (512) · md (768) · lg (1024)
121
+ * · bp1440 (1440) · bp1920 (1920)
122
+ *
123
+ * `md` and `lg` keep Tailwind's own names and values. The off-ladder defaults
124
+ * (sm/xl/2xl) are reset to `initial` so a section cannot come to depend on a
125
+ * width nobody screenshots. A swap-heavy section may still reach for a raw
126
+ * `@media` micro-breakpoint at an off-ladder width (e.g. 640 / 769).
127
+ */
128
+ --breakpoint-sm: initial;
129
+ --breakpoint-bp390: 390px;
130
+ --breakpoint-bp425: 425px;
131
+ --breakpoint-bp512: 512px;
132
+ --breakpoint-md: 768px;
133
+ --breakpoint-lg: 1024px;
134
+ --breakpoint-bp1440: 1440px;
135
+ --breakpoint-bp1920: 1920px;
136
+ --breakpoint-xl: initial;
137
+ --breakpoint-2xl: initial;
16
138
  }
package/docs/INDEX.md CHANGED
@@ -2,7 +2,7 @@
2
2
  purpose: Router for the docs corpus that ships inside the @homepages/template-kit npm package.
3
3
  status: living
4
4
  related: []
5
- updated: 2026-07-13
5
+ updated: 2026-07-14
6
6
  ---
7
7
  # template-kit docs — router
8
8
 
@@ -11,9 +11,11 @@ This tree **ships in the npm tarball** (`docs/` is listed in `package.json`'s
11
11
  LLM agents, reading it from inside `node_modules`. Keep it free of internal-only
12
12
  detail.
13
13
 
14
- Cross-service contracts, product, and data model live in `../../docs/` (umbrella)
15
- and are not shipped.
16
-
17
14
  | Doc | What it covers | Read when you're touching... |
18
15
  |---|---|---|
19
16
  | `llms.txt` | Machine-readable index of this corpus for an agent working inside an installed kit | orienting an agent in a consuming workspace |
17
+ | `schema-system.md` | The section authoring contract: `schema.ts` / `fill-spec.ts` / `fixtures.ts` / `Renderer.tsx`, `SectionProps` inference, the closed source/transform vocabulary, and the golden property fixtures | authoring or changing any section |
18
+ | `primitives.md` | The five contract primitives (`Section`, `Slot`, `SlotItem`, `SlotGroup`, `Image`) and the marker attributes they emit | writing a section's markup |
19
+ | `theme-and-css.md` | The two CSS entries, the one-Tailwind-import rule, `TokenTheme`, the breakpoint ladder, and the opt-in `content-visibility` recipe for long, image-heavy pages | a template's theme, its CSS entry, or a long page with a live map or full-screen overlay |
20
+ | `islands.md` | Interactivity: `"use client"` components, the serializable-props rule, editor options, the loader | anything interactive in a section |
21
+ | `eslint.md` | The `@homepages/template-kit/eslint` preset: zero-config setup, its glob scope, and the eleven `template-kit/*` rule ids | wiring up lint in a template workspace, or adding/changing an authoring rule |