@caltho/site-kit 0.2.9 → 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 +63 -8
  2. package/dist/cli/check.js +31 -1
  3. package/dist/content/index.d.ts +318 -23
  4. package/dist/content/index.js +148 -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 +21 -6
  77. package/dist/next/sections.js +167 -6
  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 +284 -2
  87. package/dist/sections/index.js +156 -2
  88. package/dist/sections/presets.d.ts +13 -0
  89. package/dist/sections/presets.js +300 -0
  90. package/dist/sections.css +1393 -9
  91. package/dist/sections.test.js +310 -8
  92. package/package.json +8 -3
@@ -1,20 +1,41 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { placementClass } from "../content/index.js";
3
- import { image } from "./data.js";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { buttonStyleClass, placementClass } from "../content/index.js";
3
+ import { ICONS } from "../sections/icons.js";
4
+ import { videoEmbed } from "../sections/index.js";
5
+ import { image, isDraft, lookValues } from "./data.js";
4
6
  import { Img } from "./parts/Img.js";
5
7
  import { Md } from "./parts/Md.js";
6
8
  import { Slideshow } from "./slideshow.js";
9
+ /** The kit's own classes, drawn from the Look by sections.css. */
10
+ export const KIT_CLASSES = {
11
+ section: "kit-section",
12
+ wrap: "kit-wrap",
13
+ headingLarge: "kit-h kit-h-large",
14
+ headingMedium: "kit-h kit-h-medium",
15
+ headingSmall: "kit-h kit-h-small",
16
+ text: "kit-text",
17
+ muted: "kit-muted",
18
+ button: "kit-button",
19
+ buttonQuiet: "kit-button kit-button-quiet",
20
+ };
7
21
  /** A block of one of the kit's types, drawn; null for any other (the site's own to draw). */
8
- export function KitSection({ s, level = 2, classes = {} }) {
22
+ export function KitSection({ s, level = 2, classes: given }) {
23
+ const classes = { ...KIT_CLASSES, ...definedOnly(given) };
9
24
  switch (s.type) {
10
25
  case "gallery":
11
26
  return _jsx(Gallery, { s: s, level: level, classes: classes });
12
27
  case "slideshow":
13
28
  return _jsx(Slides, { s: s, level: level, classes: classes });
29
+ case "own":
30
+ return _jsx(Own, { s: s, level: level, classes: classes });
14
31
  default:
15
32
  return null;
16
33
  }
17
34
  }
35
+ /** A site's classes, those it left undefined taking the kit's ("" is the site's: nothing). */
36
+ function definedOnly(c) {
37
+ return Object.fromEntries(Object.entries(c ?? {}).filter(([, v]) => typeof v === "string"));
38
+ }
18
39
  const RATIO = { square: "1/1", landscape: "4/3", portrait: "4/5", "as-taken": undefined };
19
40
  function Heading({ level, id, className, children }) {
20
41
  const H = `h${Math.min(Math.max(level, 1), 6)}`;
@@ -24,7 +45,7 @@ function Heading({ level, id, className, children }) {
24
45
  function Gallery({ s, level, classes }) {
25
46
  const columns = Number(s.columns ?? "3");
26
47
  const headingId = s.heading ? `${s.id ?? "gallery"}-heading` : undefined;
27
- return (_jsx("section", { className: [classes.section ?? "kit-section", "kit-gallery", placementClass(s)].filter(Boolean).join(" "), id: s.id, "aria-labelledby": headingId, children: _jsxs("div", { className: classes.wrap ?? "kit-wrap", children: [s.heading && (_jsx(Heading, { level: level, id: headingId, className: classes.heading ?? "kit-heading", children: s.heading })), s.intro && _jsx(Md, { text: s.intro, top: level + 1, className: "kit-intro" }), _jsx("ul", { className: `kit-gallery-grid kit-cols-${columns}`, children: s.images.map((img) => {
48
+ return (_jsx("section", { className: [classes.section, "kit-gallery", placementClass(s)].filter(Boolean).join(" "), id: s.id, "aria-labelledby": headingId, children: _jsxs("div", { className: classes.wrap, children: [s.heading && (_jsx(Heading, { level: level, id: headingId, className: classes.headingLarge, children: s.heading })), s.intro && _jsx(Md, { text: s.intro, top: level + 1, className: "kit-intro" }), _jsx("ul", { className: `kit-gallery-grid kit-cols-${columns}`, children: s.images.map((img) => {
28
49
  const copies = image(img.src).srcset;
29
50
  const largest = copies.at(-1)?.url ?? image(img.src).src;
30
51
  return (_jsx("li", { children: _jsxs("figure", { children: [_jsx("a", { href: largest, className: "kit-gallery-open", children: _jsx(Img, { src: img.src, alt: img.alt, sizes: `(min-width: 1024px) ${Math.round(100 / columns)}vw, 50vw`, ratio: RATIO[s.shape ?? "square"] }) }), img.caption && _jsx("figcaption", { children: img.caption })] }) }, img.src));
@@ -34,5 +55,145 @@ const SLIDE_RATIO = { wide: "kit-shape-wide", landscape: "kit-shape-landscape",
34
55
  /** Photos one at a time (Slideshow), each with its caption under it; its heading above when it has one. */
35
56
  function Slides({ s, level, classes }) {
36
57
  const headingId = s.heading ? `${s.id ?? "slideshow"}-heading` : undefined;
37
- return (_jsx("section", { className: [classes.section ?? "kit-section", "kit-slideshow", placementClass(s)].filter(Boolean).join(" "), id: s.id, "aria-labelledby": headingId, children: _jsxs("div", { className: classes.wrap ?? "kit-wrap", children: [s.heading && (_jsx(Heading, { level: level, id: headingId, className: classes.heading ?? "kit-heading", children: s.heading })), _jsx(Slideshow, { label: s.heading ?? "Photos", seconds: s.seconds, className: SLIDE_RATIO[s.shape ?? "wide"], children: s.images.map((img, i) => (_jsxs("figure", { children: [_jsx(Img, { src: img.src, alt: img.alt, sizes: "(min-width: 1200px) 1200px, 100vw", eager: i === 0 }), img.caption && _jsx("figcaption", { children: img.caption })] }, img.src))) })] }) }));
58
+ return (_jsx("section", { className: [classes.section, "kit-slideshow", placementClass(s)].filter(Boolean).join(" "), id: s.id, "aria-labelledby": headingId, children: _jsxs("div", { className: classes.wrap, children: [s.heading && (_jsx(Heading, { level: level, id: headingId, className: classes.headingLarge, children: s.heading })), _jsx(Slideshow, { label: s.heading ?? "Photos", seconds: s.seconds, className: SLIDE_RATIO[s.shape ?? "wide"], children: s.images.map((img, i) => (_jsxs("figure", { children: [_jsx(Img, { src: img.src, alt: img.alt, sizes: "(min-width: 1200px) 1200px, 100vw", eager: i === 0 }), img.caption && _jsx("figcaption", { children: img.caption })] }, img.src))) })] }) }));
59
+ }
60
+ const SHAPE = { "as-taken": undefined, landscape: "4/3", square: "1/1", portrait: "4/5" };
61
+ const COLUMNS = { one: 1, two: 2, three: 3, four: 4, "wide-left": 2, "wide-right": 2 };
62
+ /**
63
+ * A section of the business's own: its heading and intro (in the site's section heading style), then
64
+ * its columns of parts, laid out as chosen, each part in the site's own classes where it gave them
65
+ * and the kit's otherwise. A part's heading is the page's next level down (two down under a section
66
+ * heading). Its tone and spacing are classes (sections.css) from the site's Look: on soft, brand or
67
+ * dark it's a card inset within the content's width (its shape, unless it's a band across the
68
+ * page); a photo behind it is a picture under a scrim, the words light over it. On brand, dark or a
69
+ * photo the kit keeps the words and buttons readable over the site's own colour classes.
70
+ */
71
+ function Own({ s, level, classes }) {
72
+ const n = COLUMNS[s.layout ?? "one"] ?? 1;
73
+ const columns = s.columns.slice(0, n);
74
+ const tone = s.tone ?? "plain";
75
+ const behind = s.background;
76
+ const inverse = Boolean(behind) || tone === "brand" || tone === "dark";
77
+ const shaped = tone !== "plain" && !behind;
78
+ const align = s.align ?? "left";
79
+ const headingId = s.heading ? `${s.id ?? "own"}-heading` : undefined;
80
+ const partLevel = s.heading ? level + 1 : level;
81
+ const className = [
82
+ classes.section,
83
+ "kit-own",
84
+ `kit-own-${tone}`,
85
+ `kit-own-pad-${s.spacing ?? "normal"}`,
86
+ shaped && `kit-own-shape-${s.shape ?? "card"}`,
87
+ behind && "kit-own-behind",
88
+ inverse && "kit-own-inverse",
89
+ placementClass(s),
90
+ ]
91
+ .filter(Boolean)
92
+ .join(" ");
93
+ return (_jsxs("section", { className: className, id: s.id, "aria-labelledby": headingId, children: [behind && (_jsx("div", { className: "kit-own-bg", "aria-hidden": "true", children: _jsx(Img, { src: behind.src, alt: "", sizes: "100vw" }) })), _jsx("div", { className: classes.wrap, children: _jsxs("div", { className: "kit-own-panel", children: [(s.heading || s.intro) && (_jsxs("div", { className: `kit-own-head kit-own-align-${align}`, children: [s.heading && (_jsx(Heading, { level: level, id: headingId, className: classes.headingLarge, children: s.heading })), s.intro && _jsx(Md, { text: s.intro, top: level + 1, className: ["kit-own-intro", classes.text].filter(Boolean).join(" ") })] })), _jsx("div", { className: `kit-own-grid kit-own-${s.layout ?? "one"} kit-own-align-${align}`, children: columns.map((c, i) => (_jsx("div", { className: ["kit-own-column", c.card && "kit-own-card", c.end && "kit-own-column-end", c.place && c.place !== "top" && `kit-own-column-${c.place}`].filter(Boolean).join(" "), children: runs(c.parts).map((run) => run.buttons ? (_jsx("div", { className: "kit-own-actions", children: run.parts.map((p, j) => (_jsx(OwnPart, { p: p, level: partLevel, classes: classes, columns: n }, run.at + j))) }, run.at)) : (_jsx(OwnPart, { p: run.parts[0], level: partLevel, classes: classes, columns: n }, run.at))) }, i))) })] }) })] }));
94
+ }
95
+ /** A column's parts, buttons one after another kept together (side by side, wrapping on a phone). */
96
+ function runs(parts) {
97
+ const out = [];
98
+ parts.forEach((p, i) => {
99
+ const last = out.at(-1);
100
+ if (p.type === "button" && last?.buttons)
101
+ last.parts.push(p);
102
+ else
103
+ out.push({ at: i, buttons: p.type === "button", parts: [p] });
104
+ });
105
+ return out;
106
+ }
107
+ /** A part's words in the Look's other font, where it says. */
108
+ const fontClass = (font) => (font === "text" ? "kit-font-text" : font === "heading" ? "kit-font-heading" : undefined);
109
+ function OwnPart({ p, level, classes, columns }) {
110
+ const width = `(min-width: 1024px) ${Math.round(100 / columns)}vw, 100vw`;
111
+ switch (p.type) {
112
+ case "heading": {
113
+ // Display, page and large are the section's own heading level (display and page drawn at the
114
+ // Look's largest sizes); medium and small the levels under it.
115
+ const big = p.size === "large" || p.size === "display" || p.size === "page";
116
+ const at = level + (big ? 0 : p.size === "small" ? 2 : 1);
117
+ const cls = [big ? classes.headingLarge : p.size === "small" ? classes.headingSmall : classes.headingMedium, p.size === "display" && "kit-type-display", p.size === "page" && "kit-type-h1", fontClass(p.font)];
118
+ return (_jsx(Heading, { level: at, className: cls.filter(Boolean).join(" "), children: p.text }));
119
+ }
120
+ case "text":
121
+ return _jsx(Md, { text: p.text, top: level + 1, className: ["kit-own-text", classes.text, fontClass(p.font)].filter(Boolean).join(" ") });
122
+ case "photo":
123
+ // No photo chosen yet: a quiet box where it will go in CalOS's preview, nothing on the site.
124
+ if (!p.image)
125
+ return isDraft() ? _jsx("div", { className: `kit-own-photo-empty kit-own-shape-${p.shape ?? "as-taken"}`, "aria-hidden": "true" }) : null;
126
+ return (_jsxs("figure", { className: "kit-own-photo", children: [_jsx(Img, { src: p.image.src, alt: p.image.alt, sizes: width, ratio: SHAPE[p.shape ?? "as-taken"] }), p.image.caption && _jsx("figcaption", { children: p.image.caption })] }));
127
+ case "button": {
128
+ const quiet = p.style === "quiet";
129
+ // One of the business's own button styles, where it names one (kit-btn-3); else the theme's.
130
+ let vars = {};
131
+ try {
132
+ vars = lookValues();
133
+ }
134
+ catch {
135
+ // Drawn without the site's data (a test): only the theme's styles.
136
+ }
137
+ const own = buttonStyleClass(p.style, vars);
138
+ return (_jsx("p", { className: quiet ? "kit-own-action kit-own-action-quiet" : "kit-own-action", children: _jsx("a", { href: p.href, className: [quiet ? classes.buttonQuiet : p.style === "accent" ? `${classes.button} kit-button-accent` : classes.button, own].filter(Boolean).join(" "), children: p.label }) }));
139
+ }
140
+ case "list": {
141
+ const L = p.style === "numbers" ? "ol" : "ul";
142
+ return (_jsx(L, { className: ["kit-own-list", `kit-own-list-${p.style ?? "bullets"}`, classes.text].filter(Boolean).join(" "), children: p.items.map((item, i) => (_jsx("li", { children: item }, i))) }));
143
+ }
144
+ case "items": {
145
+ const L = p.numbered === false ? "ul" : "ol";
146
+ return (_jsx(L, { className: ["kit-own-items", p.lines === false ? "" : "kit-own-items-lines"].filter(Boolean).join(" "), children: p.items.map((it, i) => (_jsxs("li", { children: [p.numbered !== false && (_jsx("span", { className: ["kit-own-items-n", classes.muted].filter(Boolean).join(" "), "aria-hidden": "true", children: String(i + 1).padStart(2, "0") })), _jsxs("div", { children: [_jsx("h3", { className: ["kit-own-items-title", classes.headingMedium, fontClass(p.font)].filter(Boolean).join(" "), children: it.title }), it.text && _jsx("p", { className: ["kit-own-items-text", classes.muted].filter(Boolean).join(" "), children: it.text })] })] }, i))) }));
147
+ }
148
+ case "quote":
149
+ return (_jsxs("figure", { className: "kit-own-quote", children: [_jsx("blockquote", { className: [classes.text, fontClass(p.font)].filter(Boolean).join(" "), children: p.text }), p.who && _jsx("figcaption", { className: classes.muted, children: p.who })] }));
150
+ case "stats":
151
+ return (_jsx("dl", { className: "kit-own-stats", children: p.items.map((it, i) => (_jsxs("div", { children: [_jsx("dt", { className: classes.muted, children: it.label }), _jsx("dd", { className: classes.headingMedium, children: it.value })] }, i))) }));
152
+ case "price":
153
+ return (_jsxs("p", { className: "kit-own-price", children: [_jsx("span", { className: `kit-own-amount ${classes.headingMedium}`, children: p.amount }), p.per && _jsxs("span", { className: `kit-own-per ${classes.muted}`, children: [" ", p.per] }), p.note && _jsx("span", { className: `kit-own-note ${classes.muted}`, children: p.note })] }));
154
+ case "gallery": {
155
+ const n = Number(p.columns ?? "3");
156
+ return (_jsx("ul", { className: `kit-gallery-grid kit-cols-${n}`, children: p.images.map((img) => (_jsx("li", { children: _jsxs("figure", { children: [_jsx(Img, { src: img.src, alt: img.alt, sizes: `(min-width: 1024px) ${Math.round(100 / (n * columns))}vw, 50vw`, ratio: "1/1" }), img.caption && _jsx("figcaption", { children: img.caption })] }) }, img.src))) }));
157
+ }
158
+ case "slideshow":
159
+ return (_jsx(Slideshow, { label: "Photos", seconds: p.seconds, className: "kit-shape-landscape", children: p.images.map((img, i) => (_jsxs("figure", { children: [_jsx(Img, { src: img.src, alt: img.alt, sizes: width, eager: i === 0 }), img.caption && _jsx("figcaption", { children: img.caption })] }, img.src))) }));
160
+ case "space":
161
+ return _jsx("div", { "aria-hidden": "true", className: `kit-own-space kit-own-space-${p.size ?? "medium"}` });
162
+ case "table":
163
+ return _jsx(Table, { p: p, level: level, classes: classes });
164
+ case "icon":
165
+ return ICONS[p.name] ? _jsx(Icon, { name: p.name, part: true, className: `kit-own-icon kit-own-icon-${p.size ?? "medium"}` }) : null;
166
+ case "logos":
167
+ return (_jsxs("div", { className: "kit-own-logos", children: [p.title && _jsx("p", { className: ["kit-own-logos-title", classes.muted].filter(Boolean).join(" "), children: p.title }), _jsx("ul", { children: p.images.map((img, i) => (_jsx("li", { children: _jsx(Img, { src: img.src, alt: img.alt, sizes: "200px" }) }, i))) })] }));
168
+ case "video": {
169
+ const embed = p.url ? videoEmbed(p.url) : null;
170
+ // No address yet (or one that isn't a video's): a quiet box where it will go in CalOS's preview, nothing on the site.
171
+ if (!embed)
172
+ return isDraft() ? _jsx("div", { className: "kit-own-video kit-own-video-empty", "aria-hidden": "true" }) : null;
173
+ return (_jsx("div", { className: "kit-own-video", children: _jsx("iframe", { src: embed.src, title: p.title, width: 1280, height: 720, loading: "lazy", allow: "accelerometer; encrypted-media; gyroscope; picture-in-picture; fullscreen", allowFullScreen: true, referrerPolicy: "strict-origin-when-cross-origin" }) }));
174
+ }
175
+ }
176
+ }
177
+ /** One of the kit's line icons, drawn inline in the colour of what it's in; beside words, so hidden from a screen reader. */
178
+ function Icon({ name, className, part }) {
179
+ return (_jsx("svg", { "data-icon": part ? name : undefined, className: className ? `kit-icon ${className}` : "kit-icon", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", focusable: "false", children: ICONS[name].map((d, i) => (_jsx("path", { d: d }, i))) }));
180
+ }
181
+ /** A cell's words, or a tick for "yes" and a dash for "no", each read out as the word. */
182
+ function Cell({ value }) {
183
+ const v = value.trim().toLowerCase();
184
+ if (v === "yes" || v === "no")
185
+ return (_jsxs("span", { className: v === "yes" ? "kit-table-yes" : "kit-table-no", children: [_jsx(Icon, { name: v === "yes" ? "check" : "minus" }), _jsx("span", { className: "kit-sr", children: v === "yes" ? "Yes" : "No" })] }));
186
+ return _jsx(_Fragment, { children: value });
187
+ }
188
+ /**
189
+ * Rows compared across a few columns: a table on a wider screen (a heading over each column, each
190
+ * row's label heading its row), and on a phone each row a small card of its own, its label as its
191
+ * heading, then each column's heading and the row's cell under it, so nothing is read across a
192
+ * table too wide to see. One or the other shows (sections.css), never both.
193
+ */
194
+ function Table({ p, level, classes }) {
195
+ const heads = p.columns.slice(1);
196
+ const cells = (r) => heads.map((_, i) => r.cells[i] ?? "");
197
+ const picked = (i) => (p.highlight === i + 1 ? "kit-table-highlight" : undefined);
198
+ return (_jsxs("div", { className: "kit-own-table", children: [_jsx("div", { className: "kit-table-wide", children: _jsxs("table", { className: ["kit-table", classes.text].filter(Boolean).join(" "), children: [_jsx("thead", { children: _jsxs("tr", { children: [p.columns[0] ? _jsx("th", { scope: "col", children: p.columns[0] }) : _jsx("td", {}), heads.map((h, i) => (_jsx("th", { scope: "col", className: picked(i), children: h }, i)))] }) }), _jsx("tbody", { children: p.rows.map((r, j) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", children: r.label }), cells(r).map((c, i) => (_jsx("td", { className: picked(i), children: _jsx(Cell, { value: c }) }, i)))] }, j))) })] }) }), _jsx("ul", { className: "kit-table-cards", children: p.rows.map((r, j) => (_jsxs("li", { className: "kit-table-card", children: [_jsx(Heading, { level: level, className: classes.headingSmall, children: r.label }), _jsx("dl", { className: classes.text || undefined, children: cells(r).map((c, i) => (_jsxs("div", { className: picked(i), children: [_jsx("dt", { children: heads[i] }), _jsx("dd", { children: _jsx(Cell, { value: c }) })] }, i))) })] }, j))) })] }));
38
199
  }
package/dist/prepare.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Site } from "./content/index.ts";
1
+ import { type Site } from "./content/index.ts";
2
2
  export type PreparedImage = {
3
3
  width: number;
4
4
  height: number;
@@ -27,6 +27,10 @@ export type Prepared = {
27
27
  preferPicture?: boolean;
28
28
  /** Where its visits are counted, when CalOS counts them (Caltho's analytics server): finish puts its script in every page. */
29
29
  analytics?: Analytics;
30
+ /** The Look's values as CSS variables (lookVars), for a site with a Look: KitBlocks reads its colours to know a dark background. */
31
+ look?: Record<string, string>;
32
+ /** The @font-face rules for the Google fonts its Look uses, their files fetched into public/_fonts (LookStyle writes them). */
33
+ fontFaces?: string;
30
34
  };
31
35
  /** A site's visits counted: the id its pages send them with, where they're sent, and the script that sends them. */
32
36
  export type Analytics = {
package/dist/prepare.js CHANGED
@@ -19,6 +19,8 @@ import { copyFile, mkdir, readdir, readFile, writeFile } from "node:fs/promises"
19
19
  import { dirname, extname, join, relative } from "node:path";
20
20
  import sharp from "sharp";
21
21
  import { pathToFileURL } from "node:url";
22
+ import { lookVars, uploadedFontCss } from "./content/index.js";
23
+ import { fontFaceCss, fontFiles, googleFont, servedAt } from "./fonts-google.js";
22
24
  import { imagesIn, shareImageOf } from "./images.js";
23
25
  import { makeCards } from "./share.js";
24
26
  /** What CalOS says of the site's analytics, taken only when it's whole and its addresses are https (or a laptop's). */
@@ -107,7 +109,9 @@ export async function prepare(root, env = process.env, log = console.log) {
107
109
  const images = await pictures(site, imagesDir, pub, log);
108
110
  const cards = await makeCards(site, definition.share, root, pub, log);
109
111
  const preferPicture = !!definition.share && definition.share.prefer === "picture";
110
- const prepared = { site, images, csp: definition.csp, cards, ...(preferPicture ? { preferPicture } : {}), ...(editors.length ? { editors } : {}), ...(analytics ? { analytics } : {}) };
112
+ const look = definition.look ? lookVars(site.look, definition.look) : undefined;
113
+ const fontFaces = definition.look ? [await googleFontsFor(site.look, definition.look, pub, log), await uploadedFontsFor(site.look, imagesDir, pub, log)].filter(Boolean).join("\n") : "";
114
+ const prepared = { site, images, csp: definition.csp, cards, ...(look ? { look } : {}), ...(fontFaces ? { fontFaces } : {}), ...(preferPicture ? { preferPicture } : {}), ...(editors.length ? { editors } : {}), ...(analytics ? { analytics } : {}) };
111
115
  await writeFile(join(out, "prepared.json"), JSON.stringify(prepared));
112
116
  log(`site-kit: content from ${from}, ${site.pages.length} pages, ${Object.keys(images).length} images${analytics ? ", visits counted" : ""}`);
113
117
  return prepared;
@@ -217,3 +221,57 @@ async function pushSchema(contentUrl, schemaUrl, token, schema, log) {
217
221
  log(`site-kit: warning: couldn't tell CalOS the site's schema (${url}): ${err.message}`);
218
222
  }
219
223
  }
224
+ /**
225
+ * The Google fonts the Look uses (a heading or text font that isn't one of the site's own): each
226
+ * one's files fetched once into public/_fonts, so the site serves them itself, and their
227
+ * @font-face rules. A file a font doesn't have (no italic at a weight) is skipped; one that can't
228
+ * be fetched stops the build, rather than a site going out in the wrong font.
229
+ */
230
+ /**
231
+ * The fonts the business uploaded in CalOS (fetched with its pictures into content/images/fonts):
232
+ * copied to public/_fonts/own, served by the site, and their @font-face rules. A file the content
233
+ * names that isn't there stops the build, as a missing picture does.
234
+ */
235
+ async function uploadedFontsFor(look, imagesDir, pub, log) {
236
+ const rules = [];
237
+ for (const f of look?.uploadedFonts ?? []) {
238
+ for (const x of f.files) {
239
+ const from = join(imagesDir, x.src);
240
+ if (!existsSync(from))
241
+ throw new Error(`The content names a font file that isn't in content/images: ${x.src}`);
242
+ const at = join(pub, "_fonts", "own", x.src.slice("fonts/".length));
243
+ await mkdir(dirname(at), { recursive: true });
244
+ await writeFile(at, await readFile(from));
245
+ }
246
+ rules.push(uploadedFontCss(f, (src) => `/_fonts/own/${src.slice("fonts/".length)}`));
247
+ log(`site-kit: ${f.family} (uploaded), ${f.files.length} files, served by the site`);
248
+ }
249
+ return rules.join("\n");
250
+ }
251
+ async function googleFontsFor(look, o, pub, log) {
252
+ const own = o.fonts ?? {};
253
+ const names = [...new Set([look?.headingFont, look?.bodyFont].filter((n) => typeof n === "string" && !(n in own)))];
254
+ const rules = [];
255
+ for (const name of names) {
256
+ const f = googleFont(name);
257
+ if (!f)
258
+ continue;
259
+ const kept = [];
260
+ for (const x of fontFiles(f)) {
261
+ const at = join(pub, servedAt(f, x));
262
+ if (!existsSync(at)) {
263
+ const res = await fetch(x.url);
264
+ if (res.status === 404)
265
+ continue;
266
+ if (!res.ok)
267
+ throw new Error(`Couldn't fetch ${f.family} (${x.file}): ${res.status}`);
268
+ await mkdir(dirname(at), { recursive: true });
269
+ await writeFile(at, new Uint8Array(await res.arrayBuffer()));
270
+ }
271
+ kept.push(x);
272
+ }
273
+ rules.push(fontFaceCss(f, kept, (x) => servedAt(f, x)));
274
+ log(`site-kit: ${f.family}, ${kept.length} files, served by the site`);
275
+ }
276
+ return rules.join("\n");
277
+ }
@@ -23,3 +23,11 @@ export declare function fieldForImage(value: unknown, base: Path, url: string, i
23
23
  url: string;
24
24
  }[];
25
25
  }>): Path | null;
26
+ /**
27
+ * Which block drew each of `main`'s drawn children, by the block's index (null: not known). `drawn`:
28
+ * each child's words, its pictures' alt text included. One drawn for each block pairs them in order.
29
+ * Otherwise they're found by their words, in order: a block may draw nothing (a list with nothing in
30
+ * it yet) or more than one (a hero above its rows), and a child without the words to say is left
31
+ * unknown, never guessed, unless the next block has no words to find it by.
32
+ */
33
+ export declare function matchBlocks(blocks: unknown[], drawn: string[]): (number | null)[];
@@ -4,7 +4,7 @@
4
4
  /** The preview page's list of the pictures its build made (by path), as a JSON script with this id (../finish.ts). */
5
5
  export const BUILT_IMAGES = "calos-built-images";
6
6
  /** Keys that hold no words a visitor reads: never what a click on words means. */
7
- const SKIP = new Set(["type", "id", "href", "path", "style", "menuStyle", "footer", "ratio", "tone"]);
7
+ const SKIP = new Set(["type", "id", "href", "url", "path", "style", "menuStyle", "footer", "ratio", "tone"]);
8
8
  /** Words as they read on the page: Markdown's marks gone, a link's address gone, spacing and case evened. */
9
9
  export function readable(text) {
10
10
  return text
@@ -78,3 +78,35 @@ export function fieldForImage(value, base, url, images) {
78
78
  // The picture's group (its src and alt together) is what's edited.
79
79
  return hit.path.slice(0, -1).length > base.length ? hit.path.slice(0, -1) : hit.path;
80
80
  }
81
+ /**
82
+ * Which block drew each of `main`'s drawn children, by the block's index (null: not known). `drawn`:
83
+ * each child's words, its pictures' alt text included. One drawn for each block pairs them in order.
84
+ * Otherwise they're found by their words, in order: a block may draw nothing (a list with nothing in
85
+ * it yet) or more than one (a hero above its rows), and a child without the words to say is left
86
+ * unknown, never guessed, unless the next block has no words to find it by.
87
+ */
88
+ export function matchBlocks(blocks, drawn) {
89
+ if (drawn.length === blocks.length)
90
+ return blocks.map((_, i) => i);
91
+ const page = drawn.map(readable);
92
+ const words = blocks.map((b) => textsIn(b).map((t) => readable(t.text)).filter((t) => t.length >= 4));
93
+ const out = [];
94
+ let last = -1;
95
+ for (const text of page) {
96
+ // A later block whose words are here, else more of the block before (its words here too), else
97
+ // the next one when it has no words to be found by.
98
+ const shows = (i) => words[i].some((w) => text.includes(w));
99
+ let at = null;
100
+ for (let i = last + 1; i < blocks.length && at === null; i++)
101
+ if (shows(i))
102
+ at = i;
103
+ if (at === null && last >= 0 && shows(last))
104
+ at = last;
105
+ if (at === null && last + 1 < blocks.length && !words[last + 1].length)
106
+ at = last + 1;
107
+ out.push(at);
108
+ if (at !== null)
109
+ last = at;
110
+ }
111
+ return out;
112
+ }
package/dist/schema.d.ts CHANGED
@@ -58,3 +58,13 @@ export declare function checkStructure(site: {
58
58
  export declare function describePath(raw: unknown, path: PropertyKey[]): string;
59
59
  /** The last named field in a path, for a short label. */
60
60
  export declare const fieldOf: (path: (string | number)[]) => string | undefined;
61
+ /**
62
+ * What a block is called where a person sees a list of them (CalOS's sections, Cal's changes): a
63
+ * section of the business's own by its heading, else its first heading part; any other by its
64
+ * `heading`, `title` or the first line of its `headline`; else what its kind is called
65
+ * (`kindTitle`). At most 60 characters.
66
+ */
67
+ export declare function blockLabel(block: {
68
+ type?: unknown;
69
+ [field: string]: unknown;
70
+ }, kindTitle: string): string;
package/dist/schema.js CHANGED
@@ -235,3 +235,29 @@ export function describePath(raw, path) {
235
235
  }
236
236
  /** The last named field in a path, for a short label. */
237
237
  export const fieldOf = words;
238
+ /**
239
+ * What a block is called where a person sees a list of them (CalOS's sections, Cal's changes): a
240
+ * section of the business's own by its heading, else its first heading part; any other by its
241
+ * `heading`, `title` or the first line of its `headline`; else what its kind is called
242
+ * (`kindTitle`). At most 60 characters.
243
+ */
244
+ export function blockLabel(block, kindTitle) {
245
+ const str = (v) => (typeof v === "string" && v.trim() ? v.trim().split("\n")[0].trim() : undefined);
246
+ let label;
247
+ if (block.type === "own") {
248
+ label = str(block.heading);
249
+ if (!label && Array.isArray(block.columns)) {
250
+ for (const c of block.columns) {
251
+ const h = Array.isArray(c?.parts) ? c.parts.find((p) => p?.type === "heading" && str(p.text)) : undefined;
252
+ if (h) {
253
+ label = str(h.text);
254
+ break;
255
+ }
256
+ }
257
+ }
258
+ }
259
+ else
260
+ label = str(block.heading) ?? str(block.title) ?? str(block.headline);
261
+ const out = label ?? kindTitle;
262
+ return out.length > 60 ? `${out.slice(0, 59).trimEnd()}…` : out;
263
+ }
@@ -0,0 +1,47 @@
1
+ /** Each icon's strokes, as SVG path data (stroke 2, round ends and joins, no fill). */
2
+ export declare const ICONS: {
3
+ readonly check: readonly ["M20 6 9 17l-5-5"];
4
+ readonly calendar: readonly ["M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", "M16 2v4M8 2v4M3 10h18"];
5
+ readonly clock: readonly ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 6v6l4 2"];
6
+ readonly users: readonly ["M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2", "M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", "M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"];
7
+ readonly user: readonly ["M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2", "M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"];
8
+ readonly chat: readonly ["M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"];
9
+ readonly mail: readonly ["M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", "m22 6-10 7L2 6"];
10
+ readonly phone: readonly ["M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z"];
11
+ readonly "map-pin": readonly ["M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z", "M12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"];
12
+ readonly chart: readonly ["M18 20V10M12 20V4M6 20v-6"];
13
+ readonly file: readonly ["M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z", "M14 2v6h6M16 13H8M16 17H8M10 9H8"];
14
+ readonly folder: readonly ["M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"];
15
+ readonly invoice: readonly ["M5 2h14v20l-3-2-2 2-2-2-2 2-2-2-3 2z", "M9 7h6M9 11h6M9 15h4"];
16
+ readonly card: readonly ["M3 5h18a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z", "M1 10h22"];
17
+ readonly shield: readonly ["M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"];
18
+ readonly lock: readonly ["M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2z", "M7 11V7a5 5 0 0 1 10 0v4"];
19
+ readonly star: readonly ["m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z"];
20
+ readonly heart: readonly ["M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"];
21
+ readonly bolt: readonly ["M13 2 3 14h9l-1 8 10-12h-9z"];
22
+ readonly gear: readonly ["M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", "M12 19a7 7 0 1 0 0-14 7 7 0 0 0 0 14z", "M12 1v4M12 19v4M1 12h4M19 12h4M4.2 4.2l2.85 2.85M16.95 16.95l2.85 2.85M4.2 19.8l2.85-2.85M16.95 7.05l2.85-2.85"];
23
+ readonly search: readonly ["M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", "m21 21-4.35-4.35"];
24
+ readonly home: readonly ["m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z", "M9 22V12h6v10"];
25
+ readonly building: readonly ["M4 22V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v18", "M2 22h20", "M9 6h1M14 6h1M9 10h1M14 10h1M9 14h1M14 14h1", "M10 22v-4h4v4"];
26
+ readonly truck: readonly ["M1 3h15v13H1z", "M16 8h4l3 3v5h-7z", "M5.5 21a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z", "M18.5 21a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"];
27
+ readonly wrench: readonly ["M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"];
28
+ readonly leaf: readonly ["M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z", "M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"];
29
+ readonly sun: readonly ["M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z", "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"];
30
+ readonly globe: readonly ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M2 12h20", "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"];
31
+ readonly link: readonly ["M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"];
32
+ readonly camera: readonly ["M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z", "M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"];
33
+ readonly play: readonly ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "m10 8 6 4-6 4z"];
34
+ readonly "arrow-right": readonly ["M5 12h14M12 5l7 7-7 7"];
35
+ readonly plus: readonly ["M12 5v14M5 12h14"];
36
+ readonly minus: readonly ["M5 12h14"];
37
+ readonly x: readonly ["M18 6 6 18M6 6l12 12"];
38
+ readonly info: readonly ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 16v-4M12 8h.01"];
39
+ readonly warning: readonly ["M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z", "M12 9v4M12 17h.01"];
40
+ readonly list: readonly ["M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"];
41
+ readonly grid: readonly ["M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"];
42
+ readonly tag: readonly ["M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z", "M7 7h.01"];
43
+ readonly award: readonly ["M12 15a7 7 0 1 0 0-14 7 7 0 0 0 0 14z", "M8.21 13.89 7 23l5-3 5 3-1.21-9.12"];
44
+ };
45
+ export type IconName = keyof typeof ICONS;
46
+ /** Every icon's name, in the order CalOS offers them. */
47
+ export declare const ICON_NAMES: [IconName, ...IconName[]];
@@ -0,0 +1,51 @@
1
+ // The kit's icons: plain line drawings on a 24-unit grid, drawn inline (an `own` section's icon part,
2
+ // a table's yes and no) so they need nothing fetched and take the colour of what they're in. No
3
+ // sparkles: an icon says what something is, not that it's new or clever.
4
+ /** Each icon's strokes, as SVG path data (stroke 2, round ends and joins, no fill). */
5
+ export const ICONS = {
6
+ check: ["M20 6 9 17l-5-5"],
7
+ calendar: ["M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", "M16 2v4M8 2v4M3 10h18"],
8
+ clock: ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 6v6l4 2"],
9
+ users: ["M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2", "M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z", "M23 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75"],
10
+ user: ["M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2", "M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"],
11
+ chat: ["M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"],
12
+ mail: ["M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z", "m22 6-10 7L2 6"],
13
+ phone: [
14
+ "M22 16.9v3a2 2 0 0 1-2.2 2 19.8 19.8 0 0 1-8.6-3.1 19.5 19.5 0 0 1-6-6A19.8 19.8 0 0 1 2.1 4.2 2 2 0 0 1 4.1 2h3a2 2 0 0 1 2 1.7c.1 1 .4 1.9.7 2.8a2 2 0 0 1-.5 2.1L8 9.9a16 16 0 0 0 6 6l1.3-1.3a2 2 0 0 1 2.1-.4c.9.3 1.8.6 2.8.7a2 2 0 0 1 1.7 2z",
15
+ ],
16
+ "map-pin": ["M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z", "M12 13a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"],
17
+ chart: ["M18 20V10M12 20V4M6 20v-6"],
18
+ file: ["M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z", "M14 2v6h6M16 13H8M16 17H8M10 9H8"],
19
+ folder: ["M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"],
20
+ invoice: ["M5 2h14v20l-3-2-2 2-2-2-2 2-2-2-3 2z", "M9 7h6M9 11h6M9 15h4"],
21
+ card: ["M3 5h18a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2z", "M1 10h22"],
22
+ shield: ["M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"],
23
+ lock: ["M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2z", "M7 11V7a5 5 0 0 1 10 0v4"],
24
+ star: ["m12 2 3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01z"],
25
+ heart: ["M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"],
26
+ bolt: ["M13 2 3 14h9l-1 8 10-12h-9z"],
27
+ gear: ["M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z", "M12 19a7 7 0 1 0 0-14 7 7 0 0 0 0 14z", "M12 1v4M12 19v4M1 12h4M19 12h4M4.2 4.2l2.85 2.85M16.95 16.95l2.85 2.85M4.2 19.8l2.85-2.85M16.95 7.05l2.85-2.85"],
28
+ search: ["M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", "m21 21-4.35-4.35"],
29
+ home: ["m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z", "M9 22V12h6v10"],
30
+ building: ["M4 22V4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v18", "M2 22h20", "M9 6h1M14 6h1M9 10h1M14 10h1M9 14h1M14 14h1", "M10 22v-4h4v4"],
31
+ truck: ["M1 3h15v13H1z", "M16 8h4l3 3v5h-7z", "M5.5 21a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z", "M18.5 21a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"],
32
+ wrench: ["M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"],
33
+ leaf: ["M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.48 19 2c1 2 2 4.18 2 8 0 5.5-4.78 10-10 10z", "M2 21c0-3 1.85-5.36 5.08-6C9.5 14.52 12 13 13 12"],
34
+ sun: ["M12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10z", "M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"],
35
+ globe: ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M2 12h20", "M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"],
36
+ link: ["M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71", "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"],
37
+ camera: ["M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z", "M12 17a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"],
38
+ play: ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "m10 8 6 4-6 4z"],
39
+ "arrow-right": ["M5 12h14M12 5l7 7-7 7"],
40
+ plus: ["M12 5v14M5 12h14"],
41
+ minus: ["M5 12h14"],
42
+ x: ["M18 6 6 18M6 6l12 12"],
43
+ info: ["M12 22a10 10 0 1 0 0-20 10 10 0 0 0 0 20z", "M12 16v-4M12 8h.01"],
44
+ warning: ["M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z", "M12 9v4M12 17h.01"],
45
+ list: ["M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"],
46
+ grid: ["M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"],
47
+ tag: ["M20.59 13.41 13.42 20.58a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z", "M7 7h.01"],
48
+ award: ["M12 15a7 7 0 1 0 0-14 7 7 0 0 0 0 14z", "M8.21 13.89 7 23l5-3 5 3-1.21-9.12"],
49
+ };
50
+ /** Every icon's name, in the order CalOS offers them. */
51
+ export const ICON_NAMES = Object.keys(ICONS);