@caltho/site-kit 0.2.10 → 0.2.11

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 (92) hide show
  1. package/README.md +61 -12
  2. package/dist/cli/check.js +31 -1
  3. package/dist/content/index.d.ts +310 -23
  4. package/dist/content/index.js +142 -15
  5. package/dist/content/look.d.ts +260 -9
  6. package/dist/content/look.js +386 -22
  7. package/dist/finish.js +9 -1
  8. package/dist/fonts/families/dm-sans/LICENSE +93 -0
  9. package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-italic.woff2 +0 -0
  10. package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-normal.woff2 +0 -0
  11. package/dist/fonts/families/dm-sans/dm-sans-latin-wght-italic.woff2 +0 -0
  12. package/dist/fonts/families/dm-sans/dm-sans-latin-wght-normal.woff2 +0 -0
  13. package/dist/fonts/families/fraunces/LICENSE +93 -0
  14. package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-italic.woff2 +0 -0
  15. package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-normal.woff2 +0 -0
  16. package/dist/fonts/families/fraunces/fraunces-latin-wght-italic.woff2 +0 -0
  17. package/dist/fonts/families/fraunces/fraunces-latin-wght-normal.woff2 +0 -0
  18. package/dist/fonts/families/inter/LICENSE +93 -0
  19. package/dist/fonts/families/inter/inter-latin-ext-wght-italic.woff2 +0 -0
  20. package/dist/fonts/families/inter/inter-latin-ext-wght-normal.woff2 +0 -0
  21. package/dist/fonts/families/inter/inter-latin-wght-italic.woff2 +0 -0
  22. package/dist/fonts/families/inter/inter-latin-wght-normal.woff2 +0 -0
  23. package/dist/fonts/families/lora/LICENSE +93 -0
  24. package/dist/fonts/families/lora/lora-latin-ext-wght-italic.woff2 +0 -0
  25. package/dist/fonts/families/lora/lora-latin-ext-wght-normal.woff2 +0 -0
  26. package/dist/fonts/families/lora/lora-latin-wght-italic.woff2 +0 -0
  27. package/dist/fonts/families/lora/lora-latin-wght-normal.woff2 +0 -0
  28. package/dist/fonts/families/manrope/LICENSE +93 -0
  29. package/dist/fonts/families/manrope/manrope-latin-ext-wght-normal.woff2 +0 -0
  30. package/dist/fonts/families/manrope/manrope-latin-wght-normal.woff2 +0 -0
  31. package/dist/fonts/families/newsreader/LICENSE +93 -0
  32. package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-italic.woff2 +0 -0
  33. package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-normal.woff2 +0 -0
  34. package/dist/fonts/families/newsreader/newsreader-latin-wght-italic.woff2 +0 -0
  35. package/dist/fonts/families/newsreader/newsreader-latin-wght-normal.woff2 +0 -0
  36. package/dist/fonts/families/outfit/LICENSE +93 -0
  37. package/dist/fonts/families/outfit/outfit-latin-ext-wght-normal.woff2 +0 -0
  38. package/dist/fonts/families/outfit/outfit-latin-wght-normal.woff2 +0 -0
  39. package/dist/fonts/families/playfair-display/LICENSE +93 -0
  40. package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-italic.woff2 +0 -0
  41. package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-normal.woff2 +0 -0
  42. package/dist/fonts/families/playfair-display/playfair-display-latin-wght-italic.woff2 +0 -0
  43. package/dist/fonts/families/playfair-display/playfair-display-latin-wght-normal.woff2 +0 -0
  44. package/dist/fonts/families/source-serif-4/LICENSE +93 -0
  45. package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
  46. package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
  47. package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-italic.woff2 +0 -0
  48. package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-normal.woff2 +0 -0
  49. package/dist/fonts/families/work-sans/LICENSE +93 -0
  50. package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-italic.woff2 +0 -0
  51. package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-normal.woff2 +0 -0
  52. package/dist/fonts/families/work-sans/work-sans-latin-wght-italic.woff2 +0 -0
  53. package/dist/fonts/families/work-sans/work-sans-latin-wght-normal.woff2 +0 -0
  54. package/dist/fonts/google.json +1 -0
  55. package/dist/fonts-google.d.ts +35 -0
  56. package/dist/fonts-google.js +70 -0
  57. package/dist/fonts.css +365 -0
  58. package/dist/frame.test.d.ts +1 -0
  59. package/dist/frame.test.js +33 -0
  60. package/dist/migrate.d.ts +45 -0
  61. package/dist/migrate.js +284 -0
  62. package/dist/next/block-mark.d.ts +7 -0
  63. package/dist/next/block-mark.js +16 -0
  64. package/dist/next/blocks.d.ts +11 -0
  65. package/dist/next/blocks.js +35 -0
  66. package/dist/next/data.d.ts +4 -0
  67. package/dist/next/data.js +4 -0
  68. package/dist/next/frame.d.ts +34 -0
  69. package/dist/next/frame.js +70 -0
  70. package/dist/next/index.d.ts +6 -2
  71. package/dist/next/index.js +7 -3
  72. package/dist/next/menu-group.d.ts +11 -0
  73. package/dist/next/menu-group.js +33 -0
  74. package/dist/next/parts/LookStyle.js +3 -1
  75. package/dist/next/preview.js +282 -25
  76. package/dist/next/sections.d.ts +19 -10
  77. package/dist/next/sections.js +129 -17
  78. package/dist/prepare.d.ts +5 -1
  79. package/dist/prepare.js +59 -1
  80. package/dist/preview-match.d.ts +8 -0
  81. package/dist/preview-match.js +33 -1
  82. package/dist/schema.d.ts +10 -0
  83. package/dist/schema.js +26 -0
  84. package/dist/sections/icons.d.ts +47 -0
  85. package/dist/sections/icons.js +51 -0
  86. package/dist/sections/index.d.ts +165 -11
  87. package/dist/sections/index.js +129 -8
  88. package/dist/sections/presets.d.ts +13 -0
  89. package/dist/sections/presets.js +300 -0
  90. package/dist/sections.css +1260 -41
  91. package/dist/sections.test.js +276 -14
  92. package/package.json +8 -3
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ export { ICON_NAMES, ICONS, type IconName } from "./icons.ts";
2
3
  /** Photos in a grid, each with a caption if it has one: their work, the place, the team at it. */
3
4
  export declare const gallery: z.ZodObject<{
4
5
  heading: z.ZodOptional<z.ZodString>;
@@ -30,29 +31,50 @@ export declare const slideshow: z.ZodObject<{
30
31
  }, z.core.$strip>>;
31
32
  shape: z.ZodDefault<z.ZodEnum<{
32
33
  square: "square";
33
- wide: "wide";
34
34
  landscape: "landscape";
35
+ wide: "wide";
35
36
  }>>;
36
37
  seconds: z.ZodDefault<z.ZodNumber>;
37
38
  }, z.core.$strip>;
39
+ /**
40
+ * A YouTube or Vimeo address as the privacy-friendly player it's shown in (YouTube's no-cookie one,
41
+ * Vimeo's with tracking off), or null for any other address. Watch, share, Shorts and embed
42
+ * addresses all work.
43
+ */
44
+ export declare function videoEmbed(url: string): {
45
+ src: string;
46
+ origin: string;
47
+ } | null;
48
+ /** Where a video part's players come from: a page showing one allows them in its frame-src. */
49
+ export declare const VIDEO_ORIGINS: readonly ["https://www.youtube-nocookie.com", "https://player.vimeo.com"];
38
50
  export declare const parts: {
39
51
  readonly heading: z.ZodObject<{
40
52
  text: z.ZodString;
41
53
  size: z.ZodDefault<z.ZodEnum<{
54
+ display: "display";
42
55
  small: "small";
43
- large: "large";
44
56
  medium: "medium";
57
+ large: "large";
58
+ page: "page";
59
+ }>>;
60
+ font: z.ZodOptional<z.ZodEnum<{
61
+ heading: "heading";
62
+ text: "text";
45
63
  }>>;
46
64
  }, z.core.$strip>;
47
65
  readonly text: z.ZodObject<{
48
66
  text: z.ZodString;
67
+ font: z.ZodOptional<z.ZodEnum<{
68
+ heading: "heading";
69
+ text: "text";
70
+ }>>;
49
71
  }, z.core.$strip>;
50
72
  readonly photo: z.ZodObject<{
51
- image: z.ZodObject<{
73
+ image: z.ZodOptional<z.ZodObject<{
52
74
  src: z.ZodString;
53
75
  alt: z.ZodString;
54
76
  caption: z.ZodOptional<z.ZodString>;
55
- }, z.core.$strip>;
77
+ }, z.core.$strip>>;
56
78
  shape: z.ZodDefault<z.ZodEnum<{
57
79
  square: "square";
58
80
  landscape: "landscape";
@@ -63,10 +85,7 @@ export declare const parts: {
63
85
  readonly button: z.ZodObject<{
64
86
  label: z.ZodString;
65
87
  href: z.ZodString;
66
- style: z.ZodDefault<z.ZodEnum<{
67
- main: "main";
68
- quiet: "quiet";
69
- }>>;
88
+ style: z.ZodDefault<z.ZodString>;
70
89
  }, z.core.$strip>;
71
90
  readonly list: z.ZodObject<{
72
91
  items: z.ZodArray<z.ZodString>;
@@ -76,9 +95,25 @@ export declare const parts: {
76
95
  numbers: "numbers";
77
96
  }>>;
78
97
  }, z.core.$strip>;
98
+ readonly items: z.ZodObject<{
99
+ items: z.ZodArray<z.ZodObject<{
100
+ title: z.ZodString;
101
+ text: z.ZodOptional<z.ZodString>;
102
+ }, z.core.$strip>>;
103
+ font: z.ZodOptional<z.ZodEnum<{
104
+ heading: "heading";
105
+ text: "text";
106
+ }>>;
107
+ numbered: z.ZodDefault<z.ZodBoolean>;
108
+ lines: z.ZodDefault<z.ZodBoolean>;
109
+ }, z.core.$strip>;
79
110
  readonly quote: z.ZodObject<{
80
111
  text: z.ZodString;
81
112
  who: z.ZodOptional<z.ZodString>;
113
+ font: z.ZodOptional<z.ZodEnum<{
114
+ heading: "heading";
115
+ text: "text";
116
+ }>>;
82
117
  }, z.core.$strip>;
83
118
  readonly stats: z.ZodObject<{
84
119
  items: z.ZodArray<z.ZodObject<{
@@ -114,14 +149,85 @@ export declare const parts: {
114
149
  readonly space: z.ZodObject<{
115
150
  size: z.ZodDefault<z.ZodEnum<{
116
151
  small: "small";
152
+ medium: "medium";
117
153
  large: "large";
154
+ }>>;
155
+ }, z.core.$strip>;
156
+ readonly table: z.ZodObject<{
157
+ columns: z.ZodArray<z.ZodString>;
158
+ rows: z.ZodArray<z.ZodObject<{
159
+ label: z.ZodString;
160
+ cells: z.ZodArray<z.ZodString>;
161
+ }, z.core.$strip>>;
162
+ highlight: z.ZodOptional<z.ZodNumber>;
163
+ }, z.core.$strip>;
164
+ readonly icon: z.ZodObject<{
165
+ name: z.ZodEnum<{
166
+ card: "card";
167
+ check: "check";
168
+ phone: "phone";
169
+ file: "file";
170
+ home: "home";
171
+ play: "play";
172
+ x: "x";
173
+ search: "search";
174
+ link: "link";
175
+ calendar: "calendar";
176
+ clock: "clock";
177
+ users: "users";
178
+ user: "user";
179
+ chat: "chat";
180
+ mail: "mail";
181
+ "map-pin": "map-pin";
182
+ chart: "chart";
183
+ folder: "folder";
184
+ invoice: "invoice";
185
+ shield: "shield";
186
+ lock: "lock";
187
+ star: "star";
188
+ heart: "heart";
189
+ bolt: "bolt";
190
+ gear: "gear";
191
+ building: "building";
192
+ truck: "truck";
193
+ wrench: "wrench";
194
+ leaf: "leaf";
195
+ sun: "sun";
196
+ globe: "globe";
197
+ camera: "camera";
198
+ "arrow-right": "arrow-right";
199
+ plus: "plus";
200
+ minus: "minus";
201
+ info: "info";
202
+ warning: "warning";
203
+ list: "list";
204
+ grid: "grid";
205
+ tag: "tag";
206
+ award: "award";
207
+ }>;
208
+ size: z.ZodDefault<z.ZodEnum<{
209
+ small: "small";
118
210
  medium: "medium";
211
+ large: "large";
119
212
  }>>;
120
213
  }, z.core.$strip>;
214
+ readonly logos: z.ZodObject<{
215
+ title: z.ZodOptional<z.ZodString>;
216
+ images: z.ZodArray<z.ZodObject<{
217
+ src: z.ZodString;
218
+ alt: z.ZodString;
219
+ }, z.core.$strip>>;
220
+ }, z.core.$strip>;
221
+ readonly video: z.ZodObject<{
222
+ url: z.ZodOptional<z.ZodString>;
223
+ title: z.ZodString;
224
+ }, z.core.$strip>;
121
225
  };
122
226
  export type PartType = keyof typeof parts;
123
227
  /** A section of the business's own: columns of parts, laid out as chosen, in the site's design. */
124
228
  export declare const own: z.ZodObject<{
229
+ heading: z.ZodOptional<z.ZodString>;
230
+ intro: z.ZodOptional<z.ZodString>;
125
231
  layout: z.ZodDefault<z.ZodEnum<{
126
232
  one: "one";
127
233
  two: "two";
@@ -135,10 +241,32 @@ export declare const own: z.ZodObject<{
135
241
  center: "center";
136
242
  }>>;
137
243
  tone: z.ZodDefault<z.ZodEnum<{
138
- brand: "brand";
244
+ soft: "soft";
139
245
  plain: "plain";
246
+ brand: "brand";
247
+ dark: "dark";
140
248
  }>>;
249
+ shape: z.ZodDefault<z.ZodEnum<{
250
+ card: "card";
251
+ band: "band";
252
+ }>>;
253
+ spacing: z.ZodDefault<z.ZodEnum<{
254
+ normal: "normal";
255
+ roomy: "roomy";
256
+ tight: "tight";
257
+ }>>;
258
+ background: z.ZodOptional<z.ZodObject<{
259
+ src: z.ZodString;
260
+ alt: z.ZodString;
261
+ }, z.core.$strip>>;
141
262
  columns: z.ZodArray<z.ZodObject<{
263
+ card: z.ZodOptional<z.ZodBoolean>;
264
+ end: z.ZodOptional<z.ZodBoolean>;
265
+ place: z.ZodOptional<z.ZodEnum<{
266
+ top: "top";
267
+ middle: "middle";
268
+ bottom: "bottom";
269
+ }>>;
142
270
  parts: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
143
271
  }, z.core.$strip>>;
144
272
  }, z.core.$strip>;
@@ -173,12 +301,14 @@ export declare const sections: {
173
301
  }, z.core.$strip>>;
174
302
  shape: z.ZodDefault<z.ZodEnum<{
175
303
  square: "square";
176
- wide: "wide";
177
304
  landscape: "landscape";
305
+ wide: "wide";
178
306
  }>>;
179
307
  seconds: z.ZodDefault<z.ZodNumber>;
180
308
  }, z.core.$strip>;
181
309
  readonly own: z.ZodObject<{
310
+ heading: z.ZodOptional<z.ZodString>;
311
+ intro: z.ZodOptional<z.ZodString>;
182
312
  layout: z.ZodDefault<z.ZodEnum<{
183
313
  one: "one";
184
314
  two: "two";
@@ -192,13 +322,37 @@ export declare const sections: {
192
322
  center: "center";
193
323
  }>>;
194
324
  tone: z.ZodDefault<z.ZodEnum<{
195
- brand: "brand";
325
+ soft: "soft";
196
326
  plain: "plain";
327
+ brand: "brand";
328
+ dark: "dark";
197
329
  }>>;
330
+ shape: z.ZodDefault<z.ZodEnum<{
331
+ card: "card";
332
+ band: "band";
333
+ }>>;
334
+ spacing: z.ZodDefault<z.ZodEnum<{
335
+ normal: "normal";
336
+ roomy: "roomy";
337
+ tight: "tight";
338
+ }>>;
339
+ background: z.ZodOptional<z.ZodObject<{
340
+ src: z.ZodString;
341
+ alt: z.ZodString;
342
+ }, z.core.$strip>>;
198
343
  columns: z.ZodArray<z.ZodObject<{
344
+ card: z.ZodOptional<z.ZodBoolean>;
345
+ end: z.ZodOptional<z.ZodBoolean>;
346
+ place: z.ZodOptional<z.ZodEnum<{
347
+ top: "top";
348
+ middle: "middle";
349
+ bottom: "bottom";
350
+ }>>;
199
351
  parts: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
200
352
  }, z.core.$strip>>;
201
353
  }, z.core.$strip>;
202
354
  };
203
355
  export type KitSectionType = keyof typeof sections;
204
356
  export declare const isKitSection: (type: string) => type is KitSectionType;
357
+ /** Ready-made sections of the business's own, to add and then edit like any other (./presets.ts). */
358
+ export { presets, type OwnBlock, type Preset } from "./presets.ts";
@@ -2,8 +2,11 @@
2
2
  // its own (README, "Sections"): each one's fields here, drawn by KitSection (../next/sections.tsx) and
3
3
  // styled by sections.css, themed by the site's Look. A site takes them all into its blocks
4
4
  // (`defineSite({ ...blocks, ...sections })`); one of its own by the same name is kept over the kit's.
5
+ import { buttonStyleName } from "../content/look.js";
5
6
  import { z } from "zod";
6
7
  import { href, image, md, text } from "../content/index.js";
8
+ import { ICON_NAMES } from "./icons.js";
9
+ export { ICON_NAMES, ICONS } from "./icons.js";
7
10
  /** Photos in a grid, each with a caption if it has one: their work, the place, the team at it. */
8
11
  export const gallery = z
9
12
  .object({
@@ -31,34 +34,150 @@ export const slideshow = z
31
34
  seconds: z.number().int().min(3).max(15).default(5).meta({ title: "Seconds on each photo" }),
32
35
  })
33
36
  .meta({ title: "Slideshow", description: "Photos one at a time, fading from one to the next" });
37
+ /**
38
+ * A YouTube or Vimeo address as the privacy-friendly player it's shown in (YouTube's no-cookie one,
39
+ * Vimeo's with tracking off), or null for any other address. Watch, share, Shorts and embed
40
+ * addresses all work.
41
+ */
42
+ export function videoEmbed(url) {
43
+ const yt = /^https:\/\/(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:[^#]*&)?v=|shorts\/|embed\/|live\/)|youtu\.be\/)([\w-]{11})(?![\w-])/.exec(url.trim());
44
+ if (yt)
45
+ return { src: `${YOUTUBE}/embed/${yt[1]}`, origin: YOUTUBE };
46
+ const vm = /^https:\/\/(?:www\.)?(?:vimeo\.com\/|player\.vimeo\.com\/video\/)(\d+)(?:\/([\da-f]+))?(?![\w])/.exec(url.trim());
47
+ if (vm)
48
+ return { src: `${VIMEO}/video/${vm[1]}?${vm[2] ? `h=${vm[2]}&` : ""}dnt=1`, origin: VIMEO };
49
+ return null;
50
+ }
51
+ const YOUTUBE = "https://www.youtube-nocookie.com";
52
+ const VIMEO = "https://player.vimeo.com";
53
+ /** Where a video part's players come from: a page showing one allows them in its frame-src. */
54
+ export const VIDEO_ORIGINS = [YOUTUBE, VIMEO];
55
+ const VIDEO_HELP = "A YouTube or Vimeo address, like https://www.youtube.com/watch?v=… or https://vimeo.com/…";
56
+ // The same test as videoEmbed's, as a pattern CalOS can check with from the JSON Schema alone.
57
+ const videoUrl = z
58
+ .string()
59
+ .trim()
60
+ .regex(/^https:\/\/((www\.|m\.)?(youtube\.com\/(watch\?([^#]*&)?v=|shorts\/|embed\/|live\/)|youtu\.be\/)[\w-]{11}([^\w-].*)?|(www\.)?(vimeo\.com\/|player\.vimeo\.com\/video\/)\d+([^\w].*)?)$/, { error: VIDEO_HELP })
61
+ .meta({ title: "Video address", "x-error": VIDEO_HELP });
62
+ /** A table's cell: plain words, or "yes" / "no", drawn as a tick and a dash. */
63
+ const cell = z.string().trim().max(120);
34
64
  /**
35
65
  * The parts a section of the business's own is put together from in CalOS: each drawn in the site's
36
66
  * own type, buttons and widths (the classes it gives KitSection), so what's built looks like the
37
67
  * site, not a template. A fixed set, no colours or fonts of their own.
38
68
  */
69
+ /** Which of the Look's fonts a part's words are in, where it isn't the usual (a heading in the text's font). */
70
+ const font = z.enum(["heading", "text"]).optional().meta({ title: "Font", "x-labels": { heading: "Headings' font", text: "Text font" } });
39
71
  const photo = image.extend({ caption: text.optional().meta({ title: "Caption" }) });
40
72
  export const parts = {
41
- heading: z.object({ text, size: z.enum(["large", "medium", "small"]).default("medium").meta({ title: "Size" }) }).meta({ title: "Heading" }),
42
- text: z.object({ text: md }).meta({ title: "Text" }),
43
- photo: z.object({ image: photo, shape: z.enum(["as-taken", "landscape", "square", "portrait"]).default("as-taken").meta({ title: "Shape" }) }).meta({ title: "Photo" }),
44
- button: z.object({ label: text.meta({ title: "Label" }), href, style: z.enum(["main", "quiet"]).default("main").meta({ title: "Style" }) }).meta({ title: "Button" }),
73
+ heading: z
74
+ .object({
75
+ text,
76
+ size: z
77
+ .enum(["display", "page", "large", "medium", "small"])
78
+ .default("medium")
79
+ .meta({ title: "Size", "x-labels": { display: "Display (the largest)", page: "Page heading", large: "Section heading", medium: "Medium", small: "Small" } }),
80
+ font,
81
+ })
82
+ .meta({ title: "Heading" }),
83
+ text: z.object({ text: md, font }).meta({ title: "Text" }),
84
+ photo: z.object({ image: photo.optional(), shape: z.enum(["as-taken", "landscape", "square", "portrait"]).default("as-taken").meta({ title: "Shape" }) }).meta({ title: "Photo" }),
85
+ button: z
86
+ .object({
87
+ label: text.meta({ title: "Label" }),
88
+ href,
89
+ // The theme's Main, Accent or Quiet, or one of the business's own button styles (Look › Buttons), by its slug.
90
+ style: buttonStyleName.default("main").meta({ title: "Style", "x-ui": "button-style" }),
91
+ })
92
+ .meta({ title: "Button" }),
45
93
  list: z.object({ items: z.array(text).min(1).max(20).meta({ title: "Items" }), style: z.enum(["bullets", "checks", "numbers"]).default("bullets").meta({ title: "Style" }) }).meta({ title: "List" }),
46
- quote: z.object({ text: text.meta({ title: "Quote" }), who: text.optional().meta({ title: "Who said it" }) }).meta({ title: "Quote" }),
94
+ // Things with a name and a line about each: services, steps, what's included. Numbered (01, 02…) or
95
+ // not, with a rule between them or none.
96
+ items: z
97
+ .object({
98
+ items: z
99
+ .array(z.object({ title: text.meta({ title: "Title" }), text: text.optional().meta({ title: "Line about it" }) }).meta({ title: "Item" }))
100
+ .min(1)
101
+ .max(12)
102
+ .meta({ title: "Items" }),
103
+ // Their titles in the headings' font, or the text's (a sans title under a serif heading).
104
+ font,
105
+ numbered: z.boolean().default(true).meta({ title: "Numbered (01, 02…)" }),
106
+ lines: z.boolean().default(true).meta({ title: "Lines between them" }),
107
+ })
108
+ .meta({ title: "Items with titles", description: "Services or steps: each a title and a line about it, numbered or not" }),
109
+ quote: z.object({ text: text.meta({ title: "Quote" }), who: text.optional().meta({ title: "Who said it" }), font }).meta({ title: "Quote" }),
47
110
  stats: z.object({ items: z.array(z.object({ value: text.meta({ title: "Number" }), label: text.meta({ title: "What it is" }) })).min(1).max(6).meta({ title: "Facts" }) }).meta({ title: "Facts in a row", description: "8 guests · 4 cabins · 2 baths" }),
48
111
  price: z.object({ amount: text.meta({ title: "Price" }), per: text.optional().meta({ title: "Per" }), note: text.optional().meta({ title: "Note" }) }).meta({ title: "Price" }),
49
112
  gallery: z.object({ images: z.array(photo).min(1).max(30).meta({ title: "Photos" }), columns: z.enum(["2", "3", "4"]).default("3").meta({ title: "Photos across" }) }).meta({ title: "Photos in a grid" }),
50
113
  slideshow: z.object({ images: z.array(photo).min(2).max(20).meta({ title: "Photos" }), seconds: z.number().int().min(3).max(15).default(5).meta({ title: "Seconds on each photo" }) }).meta({ title: "Slideshow" }),
51
114
  space: z.object({ size: z.enum(["small", "medium", "large"]).default("medium").meta({ title: "Size" }) }).meta({ title: "Space" }),
115
+ // Rows of the same few things compared: plans, packages, what's included. The first column is the
116
+ // rows' labels (its heading may be ""); a cell "yes" or "no" is a tick or a dash.
117
+ table: z
118
+ .object({
119
+ columns: z.array(z.string().trim().max(60)).min(2).max(5).meta({ title: "Column headings", description: "The first is over the rows' labels and may be left empty" }),
120
+ rows: z
121
+ .array(z.object({ label: text.meta({ title: "Row" }), cells: z.array(cell).max(4).meta({ title: "Cells", description: "One for each column after the first: words, or yes or no for a tick or a dash" }) }).meta({ title: "Row" }))
122
+ .min(1)
123
+ .max(20)
124
+ .meta({ title: "Rows" }),
125
+ highlight: z.number().int().min(1).max(4).optional().meta({ title: "Column to pick out", description: "Which column (counting from 1 after the rows' labels) to pick out" }),
126
+ })
127
+ .meta({ title: "Table", description: "Rows compared across a few columns: plans, packages, what's included" }),
128
+ icon: z.object({ name: z.enum(ICON_NAMES).meta({ title: "Icon", "x-ui": "icon" }), size: z.enum(["small", "medium", "large"]).default("medium").meta({ title: "Size" }) }).meta({ title: "Icon" }),
129
+ logos: z
130
+ .object({ title: text.optional().meta({ title: "Line above" }), images: z.array(image).min(2).max(12).meta({ title: "Logos" }) })
131
+ .meta({ title: "Logos", description: "A row of the logos of clients, partners or memberships" }),
132
+ // Optional while the business finds its address: a quiet box in CalOS's preview, nothing on the site.
133
+ video: z.object({ url: videoUrl.optional(), title: text.meta({ title: "What the video is", description: "Said to someone using a screen reader" }) }).meta({ title: "Video", description: "A YouTube or Vimeo video" }),
52
134
  };
53
- const part = z.discriminatedUnion("type", Object.keys(parts).map((type) => z.object({ type: z.literal(type) }).extend(parts[type].shape).meta(parts[type].meta())));
135
+ /** What a part's own fields can't say alone: a table's rows each have a cell for every column, its headings are named, and the column it picks out is one of them. */
136
+ function partProblems(p, ctx) {
137
+ if (p.type !== "table")
138
+ return;
139
+ const t = p;
140
+ const n = t.columns.length - 1;
141
+ t.columns.forEach((c, i) => {
142
+ if (i > 0 && !c)
143
+ ctx.addIssue({ code: "custom", path: ["columns", i], message: "A column needs a heading" });
144
+ });
145
+ t.rows.forEach((r, i) => {
146
+ if (r.cells.length !== n)
147
+ ctx.addIssue({ code: "custom", path: ["rows", i, "cells"], message: `One cell for each of the ${n} column${n === 1 ? "" : "s"} after the first` });
148
+ });
149
+ if (t.highlight !== undefined && t.highlight > n)
150
+ ctx.addIssue({ code: "custom", path: ["highlight"], message: `The table has ${n} column${n === 1 ? "" : "s"} after the first` });
151
+ }
152
+ const part = z.discriminatedUnion("type", Object.keys(parts).map((type) => z.object({ type: z.literal(type) }).extend(parts[type].shape).meta(parts[type].meta()).superRefine(partProblems)));
54
153
  /** A section of the business's own: columns of parts, laid out as chosen, in the site's design. */
55
154
  export const own = z
56
155
  .object({
156
+ // Its heading area: over its columns, in the site's section heading style.
157
+ heading: text.optional().meta({ title: "Heading" }),
158
+ intro: md.optional().meta({ title: "Intro" }),
57
159
  layout: z.enum(["one", "two", "three", "four", "wide-left", "wide-right"]).default("one").meta({ title: "Columns" }),
58
160
  align: z.enum(["left", "center"]).default("left").meta({ title: "Align" }),
59
- tone: z.enum(["plain", "brand"]).default("plain").meta({ title: "Background" }),
161
+ // Plain: the page's own background; soft: a quiet tint of its text colour; brand: the brand
162
+ // colour; dark: the site's ink, words light on it. A photo behind (background) is under a scrim,
163
+ // words light over it, whatever the tone.
164
+ tone: z.enum(["plain", "soft", "brand", "dark"]).default("plain").meta({ title: "Background" }),
165
+ // On soft, brand or dark: a card inset within the content's width, in the theme's corners, or a
166
+ // band the page's full width. Plain has nothing to shape.
167
+ shape: z.enum(["card", "band"]).default("card").meta({ title: "Shape" }),
168
+ spacing: z.enum(["tight", "normal", "roomy"]).default("normal").meta({ title: "Space above and below" }),
169
+ background: image.optional().meta({ title: "Photo behind" }),
60
170
  columns: z
61
- .array(z.object({ parts: z.array(part).max(12).meta({ title: "Parts" }) }).meta({ title: "Column" }))
171
+ .array(z
172
+ .object({
173
+ card: z.boolean().optional().meta({ title: "Drawn as a card" }),
174
+ // Against the far edge: a row's second half ("Where we work … Philadelphia, PA / Princeton, NJ").
175
+ end: z.boolean().optional().meta({ title: "Against the right edge" }),
176
+ // Where its parts sit when a column beside it is taller: words at the foot of a tall photo.
177
+ place: z.enum(["top", "middle", "bottom"]).optional().meta({ title: "Up and down", "x-labels": { top: "At the top", middle: "In the middle", bottom: "At the bottom" } }),
178
+ parts: z.array(part).max(12).meta({ title: "Parts" }),
179
+ })
180
+ .meta({ title: "Column" }))
62
181
  .min(1)
63
182
  .max(4)
64
183
  .meta({ title: "Columns' parts" }),
@@ -67,3 +186,5 @@ export const own = z
67
186
  /** Every section the kit draws, by the type a block names. */
68
187
  export const sections = { gallery, slideshow, own };
69
188
  export const isKitSection = (type) => Object.prototype.hasOwnProperty.call(sections, type);
189
+ /** Ready-made sections of the business's own, to add and then edit like any other (./presets.ts). */
190
+ export { presets } from "./presets.js";
@@ -0,0 +1,13 @@
1
+ import type { z } from "zod";
2
+ import type { own } from "./index.ts";
3
+ /** A section of the business's own, as content holds it. */
4
+ export type OwnBlock = {
5
+ type: "own";
6
+ } & z.input<typeof own>;
7
+ export type Preset = {
8
+ key: string;
9
+ name: string;
10
+ description: string;
11
+ block: OwnBlock;
12
+ };
13
+ export declare const presets: Preset[];