@caltho/site-kit 0.2.10 → 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/README.md +146 -16
- package/dist/behaviors/accordion.d.ts +4 -0
- package/dist/behaviors/accordion.js +56 -0
- package/dist/behaviors/carousel.d.ts +4 -0
- package/dist/behaviors/carousel.js +127 -0
- package/dist/behaviors/filter.d.ts +1 -0
- package/dist/behaviors/filter.js +25 -0
- package/dist/behaviors/index.d.ts +14 -0
- package/dist/behaviors/index.js +78 -0
- package/dist/behaviors/motion.d.ts +13 -0
- package/dist/behaviors/motion.js +88 -0
- package/dist/behaviors/tabs.d.ts +3 -0
- package/dist/behaviors/tabs.js +55 -0
- package/dist/built/css.d.ts +2 -0
- package/dist/built/css.js +26 -0
- package/dist/built/theme.d.ts +19 -0
- package/dist/built/theme.js +86 -0
- package/dist/built/tree.d.ts +24 -0
- package/dist/built/tree.js +96 -0
- package/dist/built.test.d.ts +1 -0
- package/dist/built.test.js +257 -0
- package/dist/cli/check.js +31 -1
- package/dist/consent.d.ts +33 -0
- package/dist/consent.js +36 -0
- package/dist/consent.test.d.ts +1 -0
- package/dist/consent.test.js +61 -0
- package/dist/content/index.d.ts +450 -33
- package/dist/content/index.js +297 -15
- package/dist/content/lists.d.ts +69 -0
- package/dist/content/lists.js +77 -0
- package/dist/content/lists.test.d.ts +1 -0
- package/dist/content/lists.test.js +257 -0
- package/dist/content/look.d.ts +260 -9
- package/dist/content/look.js +386 -22
- package/dist/content-migrate.d.ts +51 -0
- package/dist/content-migrate.js +69 -0
- package/dist/finish.d.ts +1 -1
- package/dist/finish.js +27 -6
- package/dist/fonts/families/dm-sans/LICENSE +93 -0
- package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/dm-sans/dm-sans-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/dm-sans/dm-sans-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/fraunces/LICENSE +93 -0
- package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/fraunces/fraunces-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/fraunces/fraunces-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/inter/LICENSE +93 -0
- package/dist/fonts/families/inter/inter-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/inter/inter-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/inter/inter-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/inter/inter-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/lora/LICENSE +93 -0
- package/dist/fonts/families/lora/lora-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/lora/lora-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/lora/lora-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/lora/lora-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/manrope/LICENSE +93 -0
- package/dist/fonts/families/manrope/manrope-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/manrope/manrope-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/newsreader/LICENSE +93 -0
- package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/newsreader/newsreader-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/newsreader/newsreader-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/outfit/LICENSE +93 -0
- package/dist/fonts/families/outfit/outfit-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/outfit/outfit-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/playfair-display/LICENSE +93 -0
- package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/playfair-display/playfair-display-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/playfair-display/playfair-display-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/source-serif-4/LICENSE +93 -0
- package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/families/work-sans/LICENSE +93 -0
- package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-italic.woff2 +0 -0
- package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-normal.woff2 +0 -0
- package/dist/fonts/families/work-sans/work-sans-latin-wght-italic.woff2 +0 -0
- package/dist/fonts/families/work-sans/work-sans-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/google.json +1 -0
- package/dist/fonts-google.d.ts +35 -0
- package/dist/fonts-google.js +70 -0
- package/dist/fonts.css +365 -0
- package/dist/frame.test.d.ts +1 -0
- package/dist/frame.test.js +58 -0
- package/dist/migrate.d.ts +45 -0
- package/dist/migrate.js +284 -0
- package/dist/next/block-mark.d.ts +7 -0
- package/dist/next/block-mark.js +16 -0
- package/dist/next/blocks.d.ts +11 -0
- package/dist/next/blocks.js +35 -0
- package/dist/next/built.d.ts +40 -0
- package/dist/next/built.js +318 -0
- package/dist/next/consent.d.ts +4 -0
- package/dist/next/consent.js +13 -0
- package/dist/next/data.d.ts +6 -2
- package/dist/next/data.js +10 -4
- package/dist/next/frame.d.ts +42 -0
- package/dist/next/frame.js +73 -0
- package/dist/next/index.d.ts +8 -2
- package/dist/next/index.js +9 -3
- package/dist/next/menu-group.d.ts +11 -0
- package/dist/next/menu-group.js +33 -0
- package/dist/next/metadata.js +3 -2
- package/dist/next/parts/Img.d.ts +6 -1
- package/dist/next/parts/Img.js +11 -3
- package/dist/next/parts/LookStyle.js +3 -1
- package/dist/next/preview.js +511 -27
- package/dist/next/sections.d.ts +19 -10
- package/dist/next/sections.js +134 -17
- package/dist/policies.js +22 -4
- package/dist/prepare.d.ts +15 -1
- package/dist/prepare.js +125 -3
- package/dist/preview-match.d.ts +29 -0
- package/dist/preview-match.js +58 -1
- package/dist/schema.d.ts +10 -0
- package/dist/schema.js +29 -1
- package/dist/sections/icons.d.ts +49 -0
- package/dist/sections/icons.js +55 -0
- package/dist/sections/index.d.ts +188 -12
- package/dist/sections/index.js +136 -10
- package/dist/sections/presets.d.ts +13 -0
- package/dist/sections/presets.js +300 -0
- package/dist/sections.css +1584 -41
- package/dist/sections.test.js +276 -14
- package/dist/seo.js +2 -1
- package/dist/tree/behaviors.d.ts +50 -0
- package/dist/tree/behaviors.js +67 -0
- package/dist/tree/classes.d.ts +24 -0
- package/dist/tree/classes.js +94 -0
- package/dist/tree/index.d.ts +145 -0
- package/dist/tree/index.js +196 -0
- package/dist/tree/messages.d.ts +45 -0
- package/dist/tree/messages.js +7 -0
- package/package.json +22 -3
package/dist/sections/index.js
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
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
|
+
import { built, use } from "../tree/index.js";
|
|
10
|
+
export { ICON_NAMES, ICONS } from "./icons.js";
|
|
7
11
|
/** Photos in a grid, each with a caption if it has one: their work, the place, the team at it. */
|
|
8
12
|
export const gallery = z
|
|
9
13
|
.object({
|
|
@@ -31,39 +35,161 @@ export const slideshow = z
|
|
|
31
35
|
seconds: z.number().int().min(3).max(15).default(5).meta({ title: "Seconds on each photo" }),
|
|
32
36
|
})
|
|
33
37
|
.meta({ title: "Slideshow", description: "Photos one at a time, fading from one to the next" });
|
|
38
|
+
/**
|
|
39
|
+
* A YouTube or Vimeo address as the privacy-friendly player it's shown in (YouTube's no-cookie one,
|
|
40
|
+
* Vimeo's with tracking off), or null for any other address. Watch, share, Shorts and embed
|
|
41
|
+
* addresses all work.
|
|
42
|
+
*/
|
|
43
|
+
export function videoEmbed(url) {
|
|
44
|
+
const yt = /^https:\/\/(?:www\.|m\.)?(?:youtube\.com\/(?:watch\?(?:[^#]*&)?v=|shorts\/|embed\/|live\/)|youtu\.be\/)([\w-]{11})(?![\w-])/.exec(url.trim());
|
|
45
|
+
if (yt)
|
|
46
|
+
return { src: `${YOUTUBE}/embed/${yt[1]}`, origin: YOUTUBE };
|
|
47
|
+
const vm = /^https:\/\/(?:www\.)?(?:vimeo\.com\/|player\.vimeo\.com\/video\/)(\d+)(?:\/([\da-f]+))?(?![\w])/.exec(url.trim());
|
|
48
|
+
if (vm)
|
|
49
|
+
return { src: `${VIMEO}/video/${vm[1]}?${vm[2] ? `h=${vm[2]}&` : ""}dnt=1`, origin: VIMEO };
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const YOUTUBE = "https://www.youtube-nocookie.com";
|
|
53
|
+
const VIMEO = "https://player.vimeo.com";
|
|
54
|
+
/** Where a video part's players come from: a page showing one allows them in its frame-src. */
|
|
55
|
+
export const VIDEO_ORIGINS = [YOUTUBE, VIMEO];
|
|
56
|
+
const VIDEO_HELP = "A YouTube or Vimeo address, like https://www.youtube.com/watch?v=… or https://vimeo.com/…";
|
|
57
|
+
// The same test as videoEmbed's, as a pattern CalOS can check with from the JSON Schema alone.
|
|
58
|
+
const videoUrl = z
|
|
59
|
+
.string()
|
|
60
|
+
.trim()
|
|
61
|
+
.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 })
|
|
62
|
+
.meta({ title: "Video address", "x-error": VIDEO_HELP });
|
|
63
|
+
/** A table's cell: plain words, or "yes" / "no", drawn as a tick and a dash. */
|
|
64
|
+
const cell = z.string().trim().max(120);
|
|
34
65
|
/**
|
|
35
66
|
* The parts a section of the business's own is put together from in CalOS: each drawn in the site's
|
|
36
67
|
* own type, buttons and widths (the classes it gives KitSection), so what's built looks like the
|
|
37
68
|
* site, not a template. A fixed set, no colours or fonts of their own.
|
|
38
69
|
*/
|
|
70
|
+
/** 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). */
|
|
71
|
+
const font = z.enum(["heading", "text"]).optional().meta({ title: "Font", "x-labels": { heading: "Headings' font", text: "Text font" } });
|
|
39
72
|
const photo = image.extend({ caption: text.optional().meta({ title: "Caption" }) });
|
|
40
73
|
export const parts = {
|
|
41
|
-
heading: z
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
heading: z
|
|
75
|
+
.object({
|
|
76
|
+
text,
|
|
77
|
+
size: z
|
|
78
|
+
.enum(["display", "page", "large", "medium", "small"])
|
|
79
|
+
.default("medium")
|
|
80
|
+
.meta({ title: "Size", "x-labels": { display: "Display (the largest)", page: "Page heading", large: "Section heading", medium: "Medium", small: "Small" } }),
|
|
81
|
+
font,
|
|
82
|
+
})
|
|
83
|
+
.meta({ title: "Heading" }),
|
|
84
|
+
text: z.object({ text: md, font }).meta({ title: "Text" }),
|
|
85
|
+
photo: z.object({ image: photo.optional(), shape: z.enum(["as-taken", "landscape", "square", "portrait"]).default("as-taken").meta({ title: "Shape" }) }).meta({ title: "Photo" }),
|
|
86
|
+
button: z
|
|
87
|
+
.object({
|
|
88
|
+
label: text.meta({ title: "Label" }),
|
|
89
|
+
href,
|
|
90
|
+
// The theme's Main, Accent or Quiet, or one of the business's own button styles (Look › Buttons), by its slug.
|
|
91
|
+
style: buttonStyleName.default("main").meta({ title: "Style", "x-ui": "button-style" }),
|
|
92
|
+
})
|
|
93
|
+
.meta({ title: "Button" }),
|
|
45
94
|
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
|
-
|
|
95
|
+
// Things with a name and a line about each: services, steps, what's included. Numbered (01, 02…) or
|
|
96
|
+
// not, with a rule between them or none.
|
|
97
|
+
items: z
|
|
98
|
+
.object({
|
|
99
|
+
items: z
|
|
100
|
+
.array(z.object({ title: text.meta({ title: "Title" }), text: text.optional().meta({ title: "Line about it" }) }).meta({ title: "Item" }))
|
|
101
|
+
.min(1)
|
|
102
|
+
.max(12)
|
|
103
|
+
.meta({ title: "Items" }),
|
|
104
|
+
// Their titles in the headings' font, or the text's (a sans title under a serif heading).
|
|
105
|
+
font,
|
|
106
|
+
numbered: z.boolean().default(true).meta({ title: "Numbered (01, 02…)" }),
|
|
107
|
+
lines: z.boolean().default(true).meta({ title: "Lines between them" }),
|
|
108
|
+
})
|
|
109
|
+
.meta({ title: "Items with titles", description: "Services or steps: each a title and a line about it, numbered or not" }),
|
|
110
|
+
quote: z.object({ text: text.meta({ title: "Quote" }), who: text.optional().meta({ title: "Who said it" }), font }).meta({ title: "Quote" }),
|
|
47
111
|
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
112
|
price: z.object({ amount: text.meta({ title: "Price" }), per: text.optional().meta({ title: "Per" }), note: text.optional().meta({ title: "Note" }) }).meta({ title: "Price" }),
|
|
49
113
|
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
114
|
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
115
|
space: z.object({ size: z.enum(["small", "medium", "large"]).default("medium").meta({ title: "Size" }) }).meta({ title: "Space" }),
|
|
116
|
+
// Rows of the same few things compared: plans, packages, what's included. The first column is the
|
|
117
|
+
// rows' labels (its heading may be ""); a cell "yes" or "no" is a tick or a dash.
|
|
118
|
+
table: z
|
|
119
|
+
.object({
|
|
120
|
+
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" }),
|
|
121
|
+
rows: z
|
|
122
|
+
.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" }))
|
|
123
|
+
.min(1)
|
|
124
|
+
.max(20)
|
|
125
|
+
.meta({ title: "Rows" }),
|
|
126
|
+
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" }),
|
|
127
|
+
})
|
|
128
|
+
.meta({ title: "Table", description: "Rows compared across a few columns: plans, packages, what's included" }),
|
|
129
|
+
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" }),
|
|
130
|
+
logos: z
|
|
131
|
+
.object({ title: text.optional().meta({ title: "Line above" }), images: z.array(image).min(2).max(12).meta({ title: "Logos" }) })
|
|
132
|
+
.meta({ title: "Logos", description: "A row of the logos of clients, partners or memberships" }),
|
|
133
|
+
// Optional while the business finds its address: a quiet box in CalOS's preview, nothing on the site.
|
|
134
|
+
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
135
|
};
|
|
53
|
-
|
|
136
|
+
/** 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. */
|
|
137
|
+
function partProblems(p, ctx) {
|
|
138
|
+
if (p.type !== "table")
|
|
139
|
+
return;
|
|
140
|
+
const t = p;
|
|
141
|
+
const n = t.columns.length - 1;
|
|
142
|
+
t.columns.forEach((c, i) => {
|
|
143
|
+
if (i > 0 && !c)
|
|
144
|
+
ctx.addIssue({ code: "custom", path: ["columns", i], message: "A column needs a heading" });
|
|
145
|
+
});
|
|
146
|
+
t.rows.forEach((r, i) => {
|
|
147
|
+
if (r.cells.length !== n)
|
|
148
|
+
ctx.addIssue({ code: "custom", path: ["rows", i, "cells"], message: `One cell for each of the ${n} column${n === 1 ? "" : "s"} after the first` });
|
|
149
|
+
});
|
|
150
|
+
if (t.highlight !== undefined && t.highlight > n)
|
|
151
|
+
ctx.addIssue({ code: "custom", path: ["highlight"], message: `The table has ${n} column${n === 1 ? "" : "s"} after the first` });
|
|
152
|
+
}
|
|
153
|
+
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
154
|
/** A section of the business's own: columns of parts, laid out as chosen, in the site's design. */
|
|
55
155
|
export const own = z
|
|
56
156
|
.object({
|
|
157
|
+
// Its heading area: over its columns, in the site's section heading style.
|
|
158
|
+
heading: text.optional().meta({ title: "Heading" }),
|
|
159
|
+
intro: md.optional().meta({ title: "Intro" }),
|
|
57
160
|
layout: z.enum(["one", "two", "three", "four", "wide-left", "wide-right"]).default("one").meta({ title: "Columns" }),
|
|
58
161
|
align: z.enum(["left", "center"]).default("left").meta({ title: "Align" }),
|
|
59
|
-
|
|
162
|
+
// Plain: the page's own background; soft: a quiet tint of its text colour; brand: the brand
|
|
163
|
+
// colour; dark: the site's ink, words light on it. A photo behind (background) is under a scrim,
|
|
164
|
+
// words light over it, whatever the tone.
|
|
165
|
+
tone: z.enum(["plain", "soft", "brand", "dark"]).default("plain").meta({ title: "Background" }),
|
|
166
|
+
// On soft, brand or dark: a card inset within the content's width, in the theme's corners, or a
|
|
167
|
+
// band the page's full width. Plain has nothing to shape.
|
|
168
|
+
shape: z.enum(["card", "band"]).default("card").meta({ title: "Shape" }),
|
|
169
|
+
spacing: z.enum(["tight", "normal", "roomy"]).default("normal").meta({ title: "Space above and below" }),
|
|
170
|
+
background: image.optional().meta({ title: "Photo behind" }),
|
|
60
171
|
columns: z
|
|
61
|
-
.array(z
|
|
172
|
+
.array(z
|
|
173
|
+
.object({
|
|
174
|
+
card: z.boolean().optional().meta({ title: "Drawn as a card" }),
|
|
175
|
+
// Against the far edge: a row's second half ("Where we work … Philadelphia, PA / Princeton, NJ").
|
|
176
|
+
end: z.boolean().optional().meta({ title: "Against the right edge" }),
|
|
177
|
+
// Where its parts sit when a column beside it is taller: words at the foot of a tall photo.
|
|
178
|
+
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" } }),
|
|
179
|
+
parts: z.array(part).max(12).meta({ title: "Parts" }),
|
|
180
|
+
})
|
|
181
|
+
.meta({ title: "Column" }))
|
|
62
182
|
.min(1)
|
|
63
183
|
.max(4)
|
|
64
184
|
.meta({ title: "Columns' parts" }),
|
|
65
185
|
})
|
|
66
186
|
.meta({ title: "Your own section", description: "Put together from headings, text, photos, buttons and more, in the site's design" });
|
|
67
|
-
/**
|
|
68
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Every section the kit draws, by the type a block names. `built`: a tree of elements styled with
|
|
189
|
+
* Tailwind, made in CalOS's editor or by Cal (../tree, cal-os docs/design/site-builder.md); `use`:
|
|
190
|
+
* one of the site's components (a tree kept once in the content's `components`) with its slots filled.
|
|
191
|
+
*/
|
|
192
|
+
export const sections = { gallery, slideshow, own, built, use };
|
|
69
193
|
export const isKitSection = (type) => Object.prototype.hasOwnProperty.call(sections, type);
|
|
194
|
+
/** Ready-made sections of the business's own, to add and then edit like any other (./presets.ts). */
|
|
195
|
+
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[];
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// The placeholder every site has in CalOS (a soft grey with a small landscape mark): replaced in the editor.
|
|
2
|
+
const PHOTO = "photos/placeholder.jpg";
|
|
3
|
+
const LOGO = "logos/replace-me.png";
|
|
4
|
+
export const presets = [
|
|
5
|
+
{
|
|
6
|
+
key: "hero",
|
|
7
|
+
name: "Opening",
|
|
8
|
+
description: "A large heading, a line about the business, two buttons and a photo beside them",
|
|
9
|
+
block: {
|
|
10
|
+
type: "own",
|
|
11
|
+
layout: "two",
|
|
12
|
+
spacing: "roomy",
|
|
13
|
+
columns: [
|
|
14
|
+
{
|
|
15
|
+
parts: [
|
|
16
|
+
{ type: "heading", text: "Work done properly, the first time", size: "large" },
|
|
17
|
+
{ type: "text", text: "Say in a sentence or two what the business does, who it's for and where it works." },
|
|
18
|
+
{ type: "button", label: "Get in touch", href: "/", style: "main" },
|
|
19
|
+
{ type: "button", label: "See our services", href: "/", style: "quiet" },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
{ parts: [{ type: "photo", image: { src: PHOTO, alt: "" }, shape: "landscape" }] },
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "banner",
|
|
28
|
+
name: "Words over a photo",
|
|
29
|
+
description: "A heading, a line and a button over a photo, darkened so the words read",
|
|
30
|
+
block: {
|
|
31
|
+
type: "own",
|
|
32
|
+
layout: "one",
|
|
33
|
+
align: "center",
|
|
34
|
+
spacing: "roomy",
|
|
35
|
+
background: { src: PHOTO, alt: "" },
|
|
36
|
+
columns: [
|
|
37
|
+
{
|
|
38
|
+
parts: [
|
|
39
|
+
{ type: "heading", text: "Careful work, from first visit to last", size: "large" },
|
|
40
|
+
{ type: "text", text: "A line on what sets the business apart." },
|
|
41
|
+
{ type: "button", label: "Book a visit", href: "/", style: "main" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: "features",
|
|
49
|
+
name: "Three reasons",
|
|
50
|
+
description: "Three columns side by side, each a heading and a few words",
|
|
51
|
+
block: {
|
|
52
|
+
type: "own",
|
|
53
|
+
layout: "three",
|
|
54
|
+
columns: [
|
|
55
|
+
{ parts: [{ type: "heading", text: "Clear pricing", size: "small" }, { type: "text", text: "You know the cost before any work starts, in writing." }] },
|
|
56
|
+
{ parts: [{ type: "heading", text: "On time", size: "small" }, { type: "text", text: "We agree a date and keep to it, and tell you early if anything changes." }] },
|
|
57
|
+
{ parts: [{ type: "heading", text: "Easy to reach", size: "small" }, { type: "text", text: "Call or write and you hear back the same working day." }] },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "testimonial",
|
|
63
|
+
name: "What a client said",
|
|
64
|
+
description: "A client's words, centred on a quiet background",
|
|
65
|
+
block: {
|
|
66
|
+
type: "own",
|
|
67
|
+
layout: "one",
|
|
68
|
+
align: "center",
|
|
69
|
+
tone: "soft",
|
|
70
|
+
columns: [{ parts: [{ type: "quote", text: "They were careful, quick to answer and finished when they said they would. We would use them again.", who: "Client's name, their company" }] }],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "pricing",
|
|
75
|
+
name: "Prices",
|
|
76
|
+
description: "Three packages side by side, each with its price, what's included and a button",
|
|
77
|
+
block: {
|
|
78
|
+
type: "own",
|
|
79
|
+
layout: "three",
|
|
80
|
+
columns: [
|
|
81
|
+
{
|
|
82
|
+
parts: [
|
|
83
|
+
{ type: "heading", text: "Essential", size: "small" },
|
|
84
|
+
{ type: "price", amount: "Price", per: "a visit" },
|
|
85
|
+
{ type: "list", items: ["One visit", "Written quote", "Work guaranteed for a year"], style: "checks" },
|
|
86
|
+
{ type: "button", label: "Choose essential", href: "/", style: "quiet" },
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
parts: [
|
|
91
|
+
{ type: "heading", text: "Standard", size: "small" },
|
|
92
|
+
{ type: "price", amount: "Price", per: "a month", note: "Most chosen" },
|
|
93
|
+
{ type: "list", items: ["Up to three visits", "Priority booking", "Work guaranteed for two years"], style: "checks" },
|
|
94
|
+
{ type: "button", label: "Choose standard", href: "/", style: "main" },
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
parts: [
|
|
99
|
+
{ type: "heading", text: "Complete", size: "small" },
|
|
100
|
+
{ type: "price", amount: "Price", per: "a month" },
|
|
101
|
+
{ type: "list", items: ["Unlimited visits", "Same-day response", "A named contact"], style: "checks" },
|
|
102
|
+
{ type: "button", label: "Choose complete", href: "/", style: "quiet" },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "call-to-action",
|
|
110
|
+
name: "Ask to get in touch",
|
|
111
|
+
description: "A heading and a button on the brand colour, to end a page",
|
|
112
|
+
block: {
|
|
113
|
+
type: "own",
|
|
114
|
+
layout: "one",
|
|
115
|
+
align: "center",
|
|
116
|
+
tone: "brand",
|
|
117
|
+
columns: [
|
|
118
|
+
{
|
|
119
|
+
parts: [
|
|
120
|
+
{ type: "heading", text: "Ready when you are", size: "large" },
|
|
121
|
+
{ type: "text", text: "Tell us what you need and we'll reply within one working day." },
|
|
122
|
+
{ type: "button", label: "Get in touch", href: "/", style: "main" },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "contact",
|
|
130
|
+
name: "Contact",
|
|
131
|
+
description: "A heading, a few words and buttons to write or call",
|
|
132
|
+
block: {
|
|
133
|
+
type: "own",
|
|
134
|
+
layout: "one",
|
|
135
|
+
columns: [
|
|
136
|
+
{
|
|
137
|
+
parts: [
|
|
138
|
+
{ type: "heading", text: "Get in touch", size: "medium" },
|
|
139
|
+
{ type: "text", text: "Write or call during working hours. We reply to every message within one working day." },
|
|
140
|
+
{ type: "button", label: "Email us", href: "mailto:hello@example.com", style: "main" },
|
|
141
|
+
{ type: "button", label: "Call us", href: "tel:+15550100000", style: "quiet" },
|
|
142
|
+
],
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
key: "questions",
|
|
149
|
+
name: "Common questions",
|
|
150
|
+
description: "A heading beside a list of questions and their answers",
|
|
151
|
+
block: {
|
|
152
|
+
type: "own",
|
|
153
|
+
layout: "wide-right",
|
|
154
|
+
columns: [
|
|
155
|
+
{ parts: [{ type: "heading", text: "Common questions", size: "medium" }, { type: "text", text: "Anything else, just ask." }] },
|
|
156
|
+
{
|
|
157
|
+
parts: [
|
|
158
|
+
{
|
|
159
|
+
type: "list",
|
|
160
|
+
style: "bullets",
|
|
161
|
+
items: [
|
|
162
|
+
"How soon can you start? Usually within two weeks of agreeing the work.",
|
|
163
|
+
"Do you give written quotes? Yes, always, before any work starts.",
|
|
164
|
+
"Which areas do you cover? Say here where the business works.",
|
|
165
|
+
"How do I pay? By card or bank transfer, once the work is done.",
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
key: "photo-text",
|
|
175
|
+
name: "Photo and words",
|
|
176
|
+
description: "A photo beside a heading, a few words and a button",
|
|
177
|
+
block: {
|
|
178
|
+
type: "own",
|
|
179
|
+
layout: "two",
|
|
180
|
+
columns: [
|
|
181
|
+
{ parts: [{ type: "photo", image: { src: PHOTO, alt: "" }, shape: "square" }] },
|
|
182
|
+
{
|
|
183
|
+
parts: [
|
|
184
|
+
{ type: "heading", text: "About us", size: "medium" },
|
|
185
|
+
{ type: "text", text: "Who runs the business, how long it has been going and what it cares about." },
|
|
186
|
+
{ type: "button", label: "Meet the team", href: "/", style: "quiet" },
|
|
187
|
+
],
|
|
188
|
+
},
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: "stats",
|
|
194
|
+
name: "Facts in a row",
|
|
195
|
+
description: "A few numbers that say what the business has done",
|
|
196
|
+
block: {
|
|
197
|
+
type: "own",
|
|
198
|
+
layout: "one",
|
|
199
|
+
align: "center",
|
|
200
|
+
tone: "soft",
|
|
201
|
+
spacing: "tight",
|
|
202
|
+
columns: [
|
|
203
|
+
{
|
|
204
|
+
parts: [
|
|
205
|
+
{
|
|
206
|
+
type: "stats",
|
|
207
|
+
items: [
|
|
208
|
+
{ value: "Number", label: "years in business" },
|
|
209
|
+
{ value: "Number", label: "clients served" },
|
|
210
|
+
{ value: "Number", label: "days to hear back" },
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
key: "comparison",
|
|
220
|
+
name: "Comparison",
|
|
221
|
+
description: "A heading over a table comparing a few options, one of them picked out",
|
|
222
|
+
block: {
|
|
223
|
+
type: "own",
|
|
224
|
+
layout: "one",
|
|
225
|
+
heading: "Compare the options",
|
|
226
|
+
intro: "Say in a line how the options differ and who each one suits.",
|
|
227
|
+
columns: [
|
|
228
|
+
{
|
|
229
|
+
parts: [
|
|
230
|
+
{
|
|
231
|
+
type: "table",
|
|
232
|
+
columns: ["", "Option one", "Option two", "Option three"],
|
|
233
|
+
rows: [
|
|
234
|
+
{ label: "Price", cells: ["Price", "Price", "Price"] },
|
|
235
|
+
{ label: "Feature one", cells: ["yes", "yes", "yes"] },
|
|
236
|
+
{ label: "Feature two", cells: ["no", "yes", "yes"] },
|
|
237
|
+
{ label: "Feature three", cells: ["no", "no", "yes"] },
|
|
238
|
+
],
|
|
239
|
+
highlight: 2,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
key: "features-icons",
|
|
248
|
+
name: "Features with icons",
|
|
249
|
+
description: "Three cards side by side, each an icon, a heading and a few words",
|
|
250
|
+
block: {
|
|
251
|
+
type: "own",
|
|
252
|
+
layout: "three",
|
|
253
|
+
heading: "What you get",
|
|
254
|
+
columns: [
|
|
255
|
+
{ card: true, parts: [{ type: "icon", name: "check", size: "medium" }, { type: "heading", text: "Feature one", size: "small" }, { type: "text", text: "Say in a sentence what this means for the client." }] },
|
|
256
|
+
{ card: true, parts: [{ type: "icon", name: "clock", size: "medium" }, { type: "heading", text: "Feature two", size: "small" }, { type: "text", text: "Say in a sentence what this means for the client." }] },
|
|
257
|
+
{ card: true, parts: [{ type: "icon", name: "chat", size: "medium" }, { type: "heading", text: "Feature three", size: "small" }, { type: "text", text: "Say in a sentence what this means for the client." }] },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
key: "logo-strip",
|
|
263
|
+
name: "Logo strip",
|
|
264
|
+
description: "A row of the logos of clients, partners or memberships",
|
|
265
|
+
block: {
|
|
266
|
+
type: "own",
|
|
267
|
+
layout: "one",
|
|
268
|
+
align: "center",
|
|
269
|
+
spacing: "tight",
|
|
270
|
+
columns: [
|
|
271
|
+
{
|
|
272
|
+
parts: [
|
|
273
|
+
{
|
|
274
|
+
type: "logos",
|
|
275
|
+
title: "Who we work with",
|
|
276
|
+
images: [
|
|
277
|
+
{ src: LOGO, alt: "Company name" },
|
|
278
|
+
{ src: LOGO, alt: "Company name" },
|
|
279
|
+
{ src: LOGO, alt: "Company name" },
|
|
280
|
+
{ src: LOGO, alt: "Company name" },
|
|
281
|
+
],
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
key: "video",
|
|
290
|
+
name: "Video",
|
|
291
|
+
description: "A heading, a line and a YouTube or Vimeo video under them",
|
|
292
|
+
block: {
|
|
293
|
+
type: "own",
|
|
294
|
+
layout: "one",
|
|
295
|
+
heading: "See how it works",
|
|
296
|
+
intro: "A line on what the video shows.",
|
|
297
|
+
columns: [{ parts: [{ type: "video", title: "Video of how it works" }] }],
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
];
|