@marktiderman/genesis-sites 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +152 -8
  2. package/dist/assets/islands.77d11f5697.js +1 -0
  3. package/dist/assets/sites-forms.4e6aa42497.css +10 -0
  4. package/dist/assets/sites-forms.css +1 -1
  5. package/dist/assets/sites.css +1 -1
  6. package/dist/assets/sites.e1ffd2708b.css +2 -0
  7. package/dist/blocks.d.ts +1 -1
  8. package/dist/blocks.js +5 -1
  9. package/dist/blocks.js.map +1 -1
  10. package/dist/chunk-3VKTBE7S.js +69 -0
  11. package/dist/chunk-3VKTBE7S.js.map +1 -0
  12. package/dist/chunk-C375ZPU2.js +2241 -0
  13. package/dist/chunk-C375ZPU2.js.map +1 -0
  14. package/dist/{chunk-GA7U3CIN.js → chunk-IXMY4B56.js} +229 -10
  15. package/dist/chunk-IXMY4B56.js.map +1 -0
  16. package/dist/chunk-VZUDRPFG.js +13 -0
  17. package/dist/chunk-VZUDRPFG.js.map +1 -0
  18. package/dist/{chunk-KEF3V5ZN.js → chunk-ZGWGQGT2.js} +169 -73
  19. package/dist/chunk-ZGWGQGT2.js.map +1 -0
  20. package/dist/import.d.ts +130 -0
  21. package/dist/import.js +330 -0
  22. package/dist/import.js.map +1 -0
  23. package/dist/index.d.ts +40 -7
  24. package/dist/index.js +5 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/{registry-CdgVymsu.d.ts → registry-D_DCWRTc.d.ts} +274 -8
  27. package/dist/sections.d.ts +422 -6
  28. package/dist/sections.js +4 -2
  29. package/dist/{store-BzGw2i5J.d.ts → store-DkSycDhq.d.ts} +119 -53
  30. package/dist/supabase.d.ts +5 -5
  31. package/dist/supabase.js +115 -6
  32. package/dist/supabase.js.map +1 -1
  33. package/dist/web.d.ts +26 -8
  34. package/dist/web.js +142 -18
  35. package/dist/web.js.map +1 -1
  36. package/migrations/0001_genesis_sites.sql +64 -6
  37. package/migrations/0002_genesis_sites_posts.sql +404 -0
  38. package/migrations/tests/0001_genesis_sites.test.sql +30 -1
  39. package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
  40. package/package.json +18 -10
  41. package/dist/assets/islands.230b20b9c4.js +0 -1
  42. package/dist/assets/sites-forms.5e6ebf31c3.css +0 -10
  43. package/dist/assets/sites.b28b796c6f.css +0 -2
  44. package/dist/chunk-43XUAO63.js +0 -1255
  45. package/dist/chunk-43XUAO63.js.map +0 -1
  46. package/dist/chunk-GA7U3CIN.js.map +0 -1
  47. package/dist/chunk-KEF3V5ZN.js.map +0 -1
@@ -1,1255 +0,0 @@
1
- import { Link, Surface, Copy, Line, defineSection, Image, Href, RichText, registerBuiltInSection, isExternalHref, isSafeImageSrc, isSafeHref, safeJsonForHtml } from './chunk-KEF3V5ZN.js';
2
- import { z } from 'zod';
3
- import { BandLead, Band, Display } from '@marktiderman/genesis-ui/band';
4
- import { buttonVariants } from '@marktiderman/genesis-ui/button';
5
- import { cn } from '@marktiderman/genesis-ui/utils';
6
- import { Zap, Users, Trophy, Target, Star, Sparkles, Smile, Shield, Phone, MessageCircle, MapPin, Mail, Leaf, Heart, Dumbbell, Clock, Check, Calendar, Award, Activity, ChevronDown, Menu } from 'lucide-react';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
- import { parseForm, choiceOptions } from '@marktiderman/genesis-forms';
9
- import { Form } from '@marktiderman/genesis-forms-web';
10
- import { Input } from '@marktiderman/genesis-ui/input';
11
- import { Textarea } from '@marktiderman/genesis-ui/textarea';
12
- import { RichText as RichText$1, richTextClassName } from '@marktiderman/genesis-ui/rich-text';
13
-
14
- var Anchor = z.string().regex(/^[a-z][a-z0-9-]{0,39}$/, "An anchor is lowercase letters, digits and hyphens.").optional().meta({ title: "Anchor (for #links)" });
15
- var ICONS = {
16
- activity: Activity,
17
- award: Award,
18
- calendar: Calendar,
19
- check: Check,
20
- clock: Clock,
21
- dumbbell: Dumbbell,
22
- heart: Heart,
23
- leaf: Leaf,
24
- mail: Mail,
25
- "map-pin": MapPin,
26
- message: MessageCircle,
27
- phone: Phone,
28
- shield: Shield,
29
- smile: Smile,
30
- sparkles: Sparkles,
31
- star: Star,
32
- target: Target,
33
- trophy: Trophy,
34
- users: Users,
35
- zap: Zap
36
- };
37
- var IconName = z.enum(Object.keys(ICONS)).meta({ title: "Icon" });
38
- function resolveHref(href, ctx) {
39
- if (!isSafeHref(href)) {
40
- console.error(`[genesis-sites] refused an unsafe link on ${ctx.pagePath}: ${JSON.stringify(String(href).slice(0, 80))}`);
41
- return "#";
42
- }
43
- return ctx.href(href);
44
- }
45
- function linkAttrs(link, ctx) {
46
- const href = resolveHref(link.href, ctx);
47
- const external = isExternalHref(href);
48
- return {
49
- href,
50
- ...external && link.newTab ? { target: "_blank", rel: "noopener noreferrer" } : external ? { rel: "noopener" } : {}
51
- };
52
- }
53
- var TONE = {
54
- primary: buttonVariants({ variant: "default", size: "lg" }),
55
- secondary: buttonVariants({ variant: "secondary", size: "lg" }),
56
- outline: buttonVariants({ variant: "outline", size: "lg" }),
57
- // On an accent or inverse band: the band's foreground as the button, its background as the text.
58
- inverse: cn(buttonVariants({ variant: "default", size: "lg" }), "bg-background text-foreground hover:bg-background/90"),
59
- ghost: buttonVariants({ variant: "ghost", size: "lg" })
60
- };
61
- function ButtonLink({ link, ctx, tone = "primary", className }) {
62
- return /* @__PURE__ */ jsxs("a", { ...linkAttrs(link, ctx), className: cn(TONE[tone], "min-h-11", className), "data-gs-event": "cta", children: [
63
- link.label,
64
- link.newTab && isExternalHref(link.href) ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (opens in a new tab)" }) : null
65
- ] });
66
- }
67
- function TextLink({ link, ctx, className }) {
68
- return /* @__PURE__ */ jsxs(
69
- "a",
70
- {
71
- ...linkAttrs(link, ctx),
72
- className: cn("inline-flex min-h-6 items-center rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", className),
73
- children: [
74
- link.label,
75
- link.newTab && isExternalHref(link.href) ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (opens in a new tab)" }) : null
76
- ]
77
- }
78
- );
79
- }
80
- function Actions({ primary, secondary, ctx, onAccent: onAccent2, className }) {
81
- if (!primary && !secondary) return null;
82
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3 sm:flex-row sm:flex-wrap", className), children: [
83
- primary ? /* @__PURE__ */ jsx(ButtonLink, { link: primary, ctx, tone: onAccent2 ? "inverse" : "primary" }) : null,
84
- secondary ? /* @__PURE__ */ jsx(ButtonLink, { link: secondary, ctx, tone: onAccent2 ? "ghost" : "outline", className: onAccent2 ? "border border-current hover:bg-transparent hover:underline" : void 0 }) : null
85
- ] });
86
- }
87
- function SiteImage({ image, className, sizes, priority, ctx }) {
88
- if (!isSafeImageSrc(image.src)) {
89
- console.error(`[genesis-sites] refused an unsafe image source on ${ctx.pagePath}.`);
90
- return null;
91
- }
92
- const src = image.src.startsWith("/") ? ctx.href(image.src) : image.src;
93
- return /* @__PURE__ */ jsx(
94
- "img",
95
- {
96
- src,
97
- alt: image.decorative ? "" : image.alt,
98
- ...image.width ? { width: image.width } : {},
99
- ...image.height ? { height: image.height } : {},
100
- ...sizes ? { sizes } : {},
101
- loading: priority ? "eager" : "lazy",
102
- decoding: priority ? "sync" : "async",
103
- ...priority ? { fetchPriority: "high" } : {},
104
- style: image.focal ? { objectPosition: `${image.focal.x}% ${image.focal.y}%` } : void 0,
105
- className
106
- }
107
- );
108
- }
109
- function SectionTitle({ ctx, id, size, className, children }) {
110
- return /* @__PURE__ */ jsx(Display, { as: ctx.headingLevel === 1 ? "h1" : "h2", id, size: size ?? (ctx.headingLevel === 1 ? "xl" : "lg"), className, children });
111
- }
112
- function ItemTitle({ ctx, className, children }) {
113
- const Tag = ctx.headingLevel === 1 ? "h2" : "h3";
114
- return /* @__PURE__ */ jsx(Tag, { className: cn("font-display text-lg font-semibold leading-snug", className), children });
115
- }
116
- var titleId = (ctx, part = "title") => `${ctx.idPrefix}-${part}`;
117
- var HIDE_ON = { mobile: "max-md:hidden", desktop: "md:hidden" };
118
- function bandAttrs(node, ctx, anchor, labelled = true) {
119
- const hide = (node.hideOn ?? []).map((h) => HIDE_ON[h]).filter(Boolean).join(" ");
120
- return {
121
- ...anchor ? { id: anchor } : {},
122
- ...labelled ? { "aria-labelledby": titleId(ctx) } : {},
123
- "data-gs-section": node.type,
124
- "data-gs-variant": node.variant,
125
- className: cn(anchor ? "scroll-mt-20" : void 0, hide || void 0)
126
- };
127
- }
128
- function SectionBand({
129
- node,
130
- ctx,
131
- anchor,
132
- surface = "base",
133
- spacing = "md",
134
- width = "lg",
135
- labelled = true,
136
- className,
137
- children
138
- }) {
139
- const attrs = bandAttrs(node, ctx, anchor, labelled);
140
- return /* @__PURE__ */ jsx(Band, { ...attrs, as: labelled ? "section" : "div", surface, spacing, width, className: cn(attrs.className, className), children });
141
- }
142
- function SectionIntro({
143
- ctx,
144
- eyebrow,
145
- heading,
146
- body,
147
- align = "center",
148
- tone = "muted",
149
- size
150
- }) {
151
- if (!heading && !body) return null;
152
- return /* @__PURE__ */ jsxs("div", { className: cn("flex max-w-3xl flex-col gap-4", align === "center" ? "mx-auto items-center text-center" : "items-start text-left"), children: [
153
- eyebrow ? /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold uppercase tracking-wider", children: eyebrow }) : null,
154
- heading ? /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size, children: heading }) : null,
155
- body ? /* @__PURE__ */ jsx(BandLead, { tone, children: body }) : null
156
- ] });
157
- }
158
- function HiddenTitle({ ctx, children }) {
159
- const Tag = ctx.headingLevel === 1 ? "h1" : "h2";
160
- return /* @__PURE__ */ jsx(Tag, { id: titleId(ctx), className: "sr-only", children });
161
- }
162
- var onAccent = (surface) => surface === "accent" || surface === "inverse";
163
- var schema = z.object({
164
- heading: Line(90),
165
- body: Copy(320).optional(),
166
- primaryCta: Link,
167
- secondaryCta: Link.optional(),
168
- surface: Surface.default("accent"),
169
- anchor: Anchor
170
- });
171
- var ctaSection = defineSection({
172
- type: "cta",
173
- version: 1,
174
- label: "Call to action",
175
- description: "One clear next step: a heading, a line and a button.",
176
- category: "conversion",
177
- schema,
178
- variants: [
179
- { id: "banner", label: "Full-width banner" },
180
- { id: "card", label: "Card" },
181
- { id: "split", label: "Text left, buttons right" }
182
- ],
183
- defaultVariant: "banner",
184
- presets: [
185
- { name: "Banner", variant: "banner", props: { heading: "Ready to start?", primaryCta: { label: "Get started", href: "#contact" }, surface: "accent" } },
186
- { name: "Card", variant: "card", props: { heading: "Questions? We're here.", primaryCta: { label: "Contact us", href: "/contact" }, surface: "muted" } }
187
- ],
188
- render: ({ props, variant, node, ctx }) => {
189
- if (variant === "card") {
190
- const inverted2 = onAccent(props.surface);
191
- const card = props.surface === "accent" ? "bg-primary text-primary-foreground" : props.surface === "inverse" ? "bg-foreground text-background" : props.surface === "muted" ? "bg-muted" : "border border-border";
192
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, width: "md", children: /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center gap-6 rounded-2xl px-6 py-12 text-center sm:px-12 ${card}`, children: [
193
- /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size: "lg", children: props.heading }),
194
- props.body ? /* @__PURE__ */ jsx(BandLead, { tone: inverted2 ? "inherit" : "muted", children: props.body }) : null,
195
- /* @__PURE__ */ jsx(Actions, { primary: props.primaryCta, secondary: props.secondaryCta, ctx, onAccent: inverted2, className: "sm:justify-center" })
196
- ] }) });
197
- }
198
- const inverted = onAccent(props.surface);
199
- if (variant === "split") {
200
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-8 md:flex-row md:items-center md:justify-between", children: [
201
- /* @__PURE__ */ jsxs("div", { className: "flex max-w-2xl flex-col gap-3", children: [
202
- /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size: "lg", children: props.heading }),
203
- props.body ? /* @__PURE__ */ jsx(BandLead, { tone: inverted ? "inherit" : "muted", children: props.body }) : null
204
- ] }),
205
- /* @__PURE__ */ jsx(Actions, { primary: props.primaryCta, secondary: props.secondaryCta, ctx, onAccent: inverted, className: "shrink-0" })
206
- ] }) });
207
- }
208
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-3xl flex-col items-center gap-6 text-center", children: [
209
- /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size: "lg", children: props.heading }),
210
- props.body ? /* @__PURE__ */ jsx(BandLead, { tone: inverted ? "inherit" : "muted", children: props.body }) : null,
211
- /* @__PURE__ */ jsx(Actions, { primary: props.primaryCta, secondary: props.secondaryCta, ctx, onAccent: inverted, className: "sm:justify-center" })
212
- ] }) });
213
- }
214
- });
215
- var schema2 = z.object({
216
- heading: Line(90),
217
- body: Copy(320).optional(),
218
- items: z.array(z.object({ question: Line(160), answer: Copy(1200).min(1) })).min(1).max(30),
219
- surface: Surface.default("base"),
220
- anchor: Anchor
221
- });
222
- function Answer({ text, muted }) {
223
- return /* @__PURE__ */ jsx("div", { className: muted ? "flex flex-col gap-3 text-muted-foreground" : "flex flex-col gap-3", children: text.split(/\n{2,}/).map((p, i) => /* @__PURE__ */ jsx("p", { className: "whitespace-pre-line", children: p }, i)) });
224
- }
225
- var faqSection = defineSection({
226
- type: "faq",
227
- version: 1,
228
- label: "FAQ",
229
- description: "Questions and answers. Works without script: each answer opens in place.",
230
- category: "content",
231
- schema: schema2,
232
- variants: [
233
- { id: "accordion", label: "Questions that open" },
234
- { id: "list", label: "All answers shown, two columns" }
235
- ],
236
- defaultVariant: "accordion",
237
- presets: [
238
- {
239
- name: "FAQ",
240
- variant: "accordion",
241
- props: { heading: "Questions", items: [{ question: "A question people ask?", answer: "A short, direct answer." }, { question: "Another question?", answer: "Another answer." }] }
242
- }
243
- ],
244
- render: ({ props, variant, node, ctx }) => {
245
- const inverted = onAccent(props.surface);
246
- const intro = /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, body: props.body, tone: inverted ? "inherit" : "muted" });
247
- if (variant === "list") {
248
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
249
- intro,
250
- /* @__PURE__ */ jsx("div", { className: "mt-12 grid gap-x-12 gap-y-10 md:grid-cols-2", children: props.items.map((item, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
251
- /* @__PURE__ */ jsx(ItemTitle, { ctx, children: item.question }),
252
- /* @__PURE__ */ jsx(Answer, { text: item.answer, muted: !inverted })
253
- ] }, i)) })
254
- ] });
255
- }
256
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, width: "md", children: [
257
- intro,
258
- /* @__PURE__ */ jsx("div", { className: "mx-auto mt-10 flex max-w-3xl flex-col divide-y divide-border border-y border-border", children: props.items.map((item, i) => /* @__PURE__ */ jsxs("details", { className: "group", children: [
259
- /* @__PURE__ */ jsxs("summary", { className: "flex min-h-11 cursor-pointer list-none items-center justify-between gap-4 rounded-sm py-4 text-left font-display text-lg font-semibold focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden", children: [
260
- /* @__PURE__ */ jsx("span", { children: item.question }),
261
- /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": "true", className: "size-5 shrink-0 transition-transform group-open:rotate-180 motion-reduce:transition-none" })
262
- ] }),
263
- /* @__PURE__ */ jsx("div", { className: "pb-5", children: /* @__PURE__ */ jsx(Answer, { text: item.answer, muted: !inverted }) })
264
- ] }, i)) })
265
- ] });
266
- }
267
- });
268
- var Item = z.object({
269
- title: Line(60),
270
- body: Copy(320),
271
- icon: IconName.optional(),
272
- image: Image.optional(),
273
- link: Link.optional()
274
- });
275
- var schema3 = z.object({
276
- eyebrow: Line(60).optional(),
277
- heading: Line(90),
278
- body: Copy(320).optional(),
279
- items: z.array(Item).min(1).max(12),
280
- columns: z.union([z.literal(2), z.literal(3), z.literal(4)]).default(3),
281
- surface: Surface.default("base"),
282
- anchor: Anchor
283
- });
284
- var COLUMNS = { 2: "sm:grid-cols-2", 3: "sm:grid-cols-2 lg:grid-cols-3", 4: "sm:grid-cols-2 lg:grid-cols-4" };
285
- var featuresSection = defineSection({
286
- type: "features",
287
- version: 1,
288
- label: "Features",
289
- description: "What you offer: a few items, each with a title, a line of copy and an icon or image.",
290
- category: "content",
291
- schema: schema3,
292
- variants: [
293
- { id: "grid", label: "Cards in a grid" },
294
- { id: "list", label: "Icon list" },
295
- { id: "alternating", label: "Image and text, alternating" }
296
- ],
297
- defaultVariant: "grid",
298
- presets: [
299
- {
300
- name: "Three features",
301
- variant: "grid",
302
- props: {
303
- heading: "What you get",
304
- items: [
305
- { title: "First thing", body: "Why it matters.", icon: "check" },
306
- { title: "Second thing", body: "Why it matters.", icon: "star" },
307
- { title: "Third thing", body: "Why it matters.", icon: "heart" }
308
- ]
309
- }
310
- },
311
- {
312
- name: "Feature list",
313
- variant: "list",
314
- props: { heading: "Why people choose us", columns: 2, items: [{ title: "A reason", body: "In a sentence.", icon: "check" }, { title: "Another reason", body: "In a sentence.", icon: "check" }] }
315
- }
316
- ],
317
- render: ({ props, variant, node, ctx }) => {
318
- const inverted = onAccent(props.surface);
319
- const intro = /* @__PURE__ */ jsx(SectionIntro, { ctx, eyebrow: props.eyebrow, heading: props.heading, body: props.body, tone: inverted ? "inherit" : "muted" });
320
- const Icon = (name) => {
321
- if (!name) return null;
322
- const Glyph = ICONS[name];
323
- return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("inline-flex size-11 shrink-0 items-center justify-center rounded-lg", inverted ? "bg-background text-foreground" : "bg-primary text-primary-foreground"), children: /* @__PURE__ */ jsx(Glyph, { className: "size-5" }) });
324
- };
325
- if (variant === "alternating") {
326
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
327
- intro,
328
- /* @__PURE__ */ jsx("ul", { className: "mt-12 flex flex-col gap-12 md:gap-16", children: props.items.map((item, i) => /* @__PURE__ */ jsxs("li", { className: "grid items-center gap-8 md:grid-cols-2 md:gap-12", children: [
329
- /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-3", i % 2 === 1 && "md:order-2"), children: [
330
- Icon(item.icon),
331
- /* @__PURE__ */ jsx(ItemTitle, { ctx, className: "text-2xl", children: item.title }),
332
- /* @__PURE__ */ jsx("p", { className: inverted ? void 0 : "text-muted-foreground", children: item.body }),
333
- item.link ? /* @__PURE__ */ jsx(TextLink, { link: item.link, ctx, className: "font-medium underline" }) : null
334
- ] }),
335
- item.image ? /* @__PURE__ */ jsx(SiteImage, { image: item.image, ctx, sizes: "(min-width: 768px) 50vw, 100vw", className: "aspect-[4/3] w-full rounded-xl object-cover" }) : /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "hidden aspect-[4/3] w-full rounded-xl bg-muted md:block" })
336
- ] }, i)) })
337
- ] });
338
- }
339
- if (variant === "list") {
340
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
341
- intro,
342
- /* @__PURE__ */ jsx("ul", { className: cn("mt-12 grid gap-x-10 gap-y-8", props.columns >= 2 && "sm:grid-cols-2", props.columns >= 3 && "lg:grid-cols-3"), children: props.items.map((item, i) => /* @__PURE__ */ jsxs("li", { className: "flex gap-4", children: [
343
- Icon(item.icon ?? "check"),
344
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
345
- /* @__PURE__ */ jsx(ItemTitle, { ctx, children: item.title }),
346
- /* @__PURE__ */ jsx("p", { className: inverted ? void 0 : "text-muted-foreground", children: item.body }),
347
- item.link ? /* @__PURE__ */ jsx(TextLink, { link: item.link, ctx, className: "font-medium underline" }) : null
348
- ] })
349
- ] }, i)) })
350
- ] });
351
- }
352
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
353
- intro,
354
- /* @__PURE__ */ jsx("ul", { className: cn("mt-12 grid gap-6", COLUMNS[props.columns]), children: props.items.map((item, i) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-3 rounded-xl border border-border bg-background p-6 text-foreground", children: [
355
- item.image ? /* @__PURE__ */ jsx(SiteImage, { image: item.image, ctx, sizes: "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw", className: "aspect-video w-full rounded-lg object-cover" }) : Icon(item.icon),
356
- /* @__PURE__ */ jsx(ItemTitle, { ctx, children: item.title }),
357
- /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: item.body }),
358
- item.link ? /* @__PURE__ */ jsx(TextLink, { link: item.link, ctx, className: "mt-auto font-medium underline" }) : null
359
- ] }, i)) })
360
- ] });
361
- }
362
- });
363
- var schema4 = z.object({
364
- tagline: Copy(200).optional(),
365
- columns: z.array(z.object({ heading: Line(40), links: z.array(Link).min(1).max(8) })).max(4).default([]),
366
- links: z.array(Link).max(12).default([]),
367
- copyright: Line(120).optional(),
368
- surface: Surface.default("inverse")
369
- });
370
- var footerSection = defineSection({
371
- type: "footer",
372
- version: 1,
373
- label: "Footer",
374
- description: "The site's footer: links, a line about you and the copyright.",
375
- category: "footer",
376
- placement: "footer",
377
- limit: 1,
378
- schema: schema4,
379
- variants: [
380
- { id: "simple", label: "Links in a row" },
381
- { id: "columns", label: "Link columns" },
382
- { id: "minimal", label: "One line" }
383
- ],
384
- defaultVariant: "simple",
385
- presets: [
386
- { name: "Footer", variant: "simple", props: { links: [{ label: "Contact", href: "/contact" }], copyright: "\xA9 Your business" } },
387
- { name: "Footer with columns", variant: "columns", props: { columns: [{ heading: "Visit", links: [{ label: "Contact", href: "/contact" }] }], copyright: "\xA9 Your business" } }
388
- ],
389
- render: ({ props, variant, node, ctx }) => {
390
- const attrs = bandAttrs(node, ctx, void 0, false);
391
- const quiet = props.surface === "base" || props.surface === "muted" ? "text-muted-foreground" : void 0;
392
- const name = /* @__PURE__ */ jsx("p", { className: "font-display text-lg font-bold", children: ctx.site.name });
393
- const copyright = props.copyright ? /* @__PURE__ */ jsx("p", { className: cn("text-sm", quiet), children: props.copyright }) : null;
394
- const row = (links) => links.length ? /* @__PURE__ */ jsx("nav", { "aria-label": "Footer", children: /* @__PURE__ */ jsx("ul", { className: "flex flex-wrap gap-x-6 gap-y-2", children: links.map((link, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(TextLink, { link, ctx, className: "text-sm underline" }) }, i)) }) }) : null;
395
- if (variant === "minimal") {
396
- return /* @__PURE__ */ jsx(Band, { ...attrs, as: "footer", surface: props.surface, spacing: "sm", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between", children: [
397
- copyright ?? name,
398
- row(props.links)
399
- ] }) });
400
- }
401
- if (variant === "columns") {
402
- return /* @__PURE__ */ jsxs(Band, { ...attrs, as: "footer", surface: props.surface, spacing: "md", children: [
403
- /* @__PURE__ */ jsxs("div", { className: "grid gap-10 md:grid-cols-[2fr_repeat(3,1fr)]", children: [
404
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
405
- name,
406
- props.tagline ? /* @__PURE__ */ jsx("p", { className: cn("max-w-sm", quiet), children: props.tagline }) : null
407
- ] }),
408
- props.columns.map((col, i) => /* @__PURE__ */ jsxs("nav", { "aria-label": col.heading, className: "flex flex-col gap-3", children: [
409
- /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold uppercase tracking-wider", children: col.heading }),
410
- /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: col.links.map((link, j) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(TextLink, { link, ctx, className: "text-sm underline" }) }, j)) })
411
- ] }, i))
412
- ] }),
413
- /* @__PURE__ */ jsxs("div", { className: "mt-10 flex flex-col gap-4 border-t border-current/20 pt-6 sm:flex-row sm:items-center sm:justify-between", children: [
414
- copyright,
415
- row(props.links)
416
- ] })
417
- ] });
418
- }
419
- return /* @__PURE__ */ jsxs(Band, { ...attrs, as: "footer", surface: props.surface, spacing: "sm", children: [
420
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6 md:flex-row md:items-start md:justify-between", children: [
421
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
422
- name,
423
- props.tagline ? /* @__PURE__ */ jsx("p", { className: cn("max-w-md", quiet), children: props.tagline }) : null
424
- ] }),
425
- row(props.links)
426
- ] }),
427
- copyright ? /* @__PURE__ */ jsx("div", { className: "mt-6", children: copyright }) : null
428
- ] });
429
- }
430
- });
431
- function SiteFormHeader({ title, description, requiredNote }) {
432
- return /* @__PURE__ */ jsxs("header", { className: "flex flex-col gap-1", children: [
433
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
434
- description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null,
435
- requiredNote ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
436
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "* " }),
437
- requiredNote
438
- ] }) : null
439
- ] });
440
- }
441
- var siteFormSlots = { header: SiteFormHeader };
442
- var KINDS = {
443
- "short-text": "text",
444
- "long-text": "text",
445
- email: "text",
446
- phone: "text",
447
- date: "text",
448
- number: "number",
449
- "single-choice": "single",
450
- dropdown: "single",
451
- "multi-choice": "multi",
452
- "yes-no": "yes-no",
453
- consent: "consent"
454
- };
455
- var hasRecall = (text) => typeof text === "string" && text.includes("{{");
456
- function lightField(block) {
457
- if (block.kind === "page-break") return false;
458
- if (block.kind === "content") return (block.type === "heading" || block.type === "text") && !hasRecall(block.text);
459
- if (!(block.type in KINDS)) return false;
460
- if (hasRecall(block.label) || hasRecall(block.description) || hasRecall(block.placeholder)) return false;
461
- const props = block.props ?? {};
462
- if (props.optionsFrom !== void 0 || props.other !== void 0) return false;
463
- return true;
464
- }
465
- function isLightForm(form) {
466
- if (form.layout?.mode === "focus") return false;
467
- if (form.logic?.length || form.hidden?.length) return false;
468
- if (form.settings?.anonymous || form.settings?.saveProgress) return false;
469
- return form.blocks.length > 0 && form.blocks.every(lightField);
470
- }
471
- var control = "text-base";
472
- var choiceInput = "size-5 shrink-0 accent-[hsl(var(--primary))]";
473
- var choiceRow = "flex min-h-11 items-center gap-3 rounded-md px-1";
474
- function str(value) {
475
- return typeof value === "string" && value ? value : typeof value === "number" ? String(value) : void 0;
476
- }
477
- function FieldControl({ field, id, describedBy }) {
478
- const type = field.type;
479
- const props = field.props ?? {};
480
- const common = {
481
- id,
482
- name: field.id,
483
- required: field.required === true,
484
- ...field.placeholder ? { placeholder: field.placeholder } : {},
485
- ...describedBy ? { "aria-describedby": describedBy } : {}
486
- };
487
- switch (type) {
488
- case "short-text":
489
- return /* @__PURE__ */ jsx(Input, { ...common, type: "text", maxLength: 500, className: control });
490
- case "long-text":
491
- return /* @__PURE__ */ jsx(Textarea, { ...common, maxLength: 5e3, rows: 4, className: control });
492
- case "email":
493
- return /* @__PURE__ */ jsx(Input, { ...common, type: "email", autoComplete: "email", inputMode: "email", maxLength: 320, className: control });
494
- case "phone":
495
- return /* @__PURE__ */ jsx(Input, { ...common, type: "tel", autoComplete: "tel", inputMode: "tel", maxLength: 40, className: control });
496
- case "date":
497
- return /* @__PURE__ */ jsx(Input, { ...common, type: "date", ...str(props.min) ? { min: str(props.min) } : {}, ...str(props.max) ? { max: str(props.max) } : {}, className: control });
498
- case "number":
499
- return /* @__PURE__ */ jsx(
500
- Input,
501
- {
502
- ...common,
503
- type: "number",
504
- inputMode: "decimal",
505
- ...str(props.min) ? { min: str(props.min) } : {},
506
- ...str(props.max) ? { max: str(props.max) } : {},
507
- step: str(props.step) ?? "any",
508
- className: control
509
- }
510
- );
511
- case "dropdown":
512
- return /* @__PURE__ */ jsxs(
513
- "select",
514
- {
515
- ...common,
516
- defaultValue: "",
517
- className: "flex h-11 w-full rounded-md border border-input bg-background px-3 text-base focus-visible:outline-2 focus-visible:outline-ring",
518
- children: [
519
- /* @__PURE__ */ jsx("option", { value: "", disabled: field.required === true, children: field.placeholder || "Choose one" }),
520
- choiceOptions(field).map((o) => /* @__PURE__ */ jsx("option", { value: o.value, children: o.label }, o.value))
521
- ]
522
- }
523
- );
524
- default:
525
- return null;
526
- }
527
- }
528
- function ChoiceGroup({ field, id, describedBy }) {
529
- const type = field.type;
530
- const required = field.required === true;
531
- const options = type === "yes-no" ? [
532
- { value: "yes", label: "Yes" },
533
- { value: "no", label: "No" }
534
- ] : choiceOptions(field).map((o) => ({ value: o.value, label: o.label }));
535
- const multi = type === "multi-choice";
536
- return /* @__PURE__ */ jsxs("fieldset", { className: "flex flex-col gap-1", ...describedBy ? { "aria-describedby": describedBy } : {}, "data-gs-field": field.id, "data-gs-kind": KINDS[type], ...required ? { "data-gs-required": "" } : {}, children: [
537
- /* @__PURE__ */ jsxs("legend", { className: "mb-1 font-medium", children: [
538
- field.label,
539
- required ? /* @__PURE__ */ jsx(RequiredMark, {}) : null
540
- ] }),
541
- field.description ? /* @__PURE__ */ jsx("p", { id: `${id}-desc`, className: "mb-1 text-sm text-muted-foreground", children: field.description }) : null,
542
- options.map((o, i) => /* @__PURE__ */ jsxs("label", { className: choiceRow, children: [
543
- /* @__PURE__ */ jsx(
544
- "input",
545
- {
546
- type: multi ? "checkbox" : "radio",
547
- name: field.id,
548
- value: o.value,
549
- className: choiceInput,
550
- ...required && !multi && i === 0 ? { required: true } : {}
551
- }
552
- ),
553
- /* @__PURE__ */ jsx("span", { children: o.label })
554
- ] }, o.value))
555
- ] });
556
- }
557
- function RequiredMark() {
558
- return /* @__PURE__ */ jsxs(Fragment, { children: [
559
- /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "text-muted-foreground", children: [
560
- " ",
561
- "*"
562
- ] }),
563
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (required)" })
564
- ] });
565
- }
566
- function FieldRow({ field, idPrefix }) {
567
- const id = `${idPrefix}-${field.id}`;
568
- const type = field.type;
569
- const describedBy = field.description ? `${id}-desc` : void 0;
570
- if (type === "single-choice" || type === "multi-choice" || type === "yes-no") return /* @__PURE__ */ jsx(ChoiceGroup, { field, id, ...describedBy ? { describedBy } : {} });
571
- if (type === "consent") {
572
- const text = str(field.props?.text) ?? field.label;
573
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", "data-gs-field": field.id, "data-gs-kind": "consent", children: [
574
- /* @__PURE__ */ jsxs("label", { className: cn(choiceRow, "items-start py-1"), children: [
575
- /* @__PURE__ */ jsx("input", { id, type: "checkbox", name: field.id, value: "true", required: field.required === true, className: cn(choiceInput, "mt-0.5"), ...describedBy ? { "aria-describedby": describedBy } : {} }),
576
- /* @__PURE__ */ jsxs("span", { children: [
577
- text,
578
- field.required ? /* @__PURE__ */ jsx(RequiredMark, {}) : null
579
- ] })
580
- ] }),
581
- field.description ? /* @__PURE__ */ jsx("p", { id: `${id}-desc`, className: "text-sm text-muted-foreground", children: field.description }) : null
582
- ] });
583
- }
584
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", "data-gs-field": field.id, "data-gs-kind": KINDS[type], children: [
585
- /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "font-medium", children: [
586
- field.label,
587
- field.required ? /* @__PURE__ */ jsx(RequiredMark, {}) : null
588
- ] }),
589
- field.description ? /* @__PURE__ */ jsx("p", { id: `${id}-desc`, className: "-mt-1 text-sm text-muted-foreground", children: field.description }) : null,
590
- /* @__PURE__ */ jsx(FieldControl, { field, id, ...describedBy ? { describedBy } : {} })
591
- ] });
592
- }
593
- function LightForm({ form, slug, submitUrl, submitLabel, idPrefix }) {
594
- const ending = form.endings?.[0];
595
- const statusId = `${idPrefix}--gs-status`;
596
- return /* @__PURE__ */ jsxs(
597
- "form",
598
- {
599
- method: "post",
600
- action: submitUrl,
601
- "data-gs-island": "light-form",
602
- "data-gs-form": slug,
603
- "data-gs-sent": ending ? `${ending.title}${ending.description ? ` ${ending.description}` : ""}` : "Thanks. We got it.",
604
- className: "flex flex-col gap-5 text-left",
605
- children: [
606
- /* @__PURE__ */ jsx("input", { type: "hidden", name: "form", value: slug }),
607
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
608
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: form.title }),
609
- form.description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: form.description }) : null,
610
- form.blocks.some((b) => b.kind === "field" && b.required) ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
611
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "* " }),
612
- "Required"
613
- ] }) : null
614
- ] }),
615
- form.blocks.map((block) => {
616
- if (block.kind === "content") {
617
- return block.type === "heading" ? /* @__PURE__ */ jsx("h4", { className: "font-semibold", children: block.text }, block.id) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: block.text }, block.id);
618
- }
619
- if (block.kind === "field") return /* @__PURE__ */ jsx(FieldRow, { field: block, idPrefix }, block.id);
620
- return null;
621
- }),
622
- /* @__PURE__ */ jsx("p", { id: statusId, role: "status", "aria-live": "polite", "data-gs-status": "", className: "min-h-6 text-sm font-medium" }),
623
- /* @__PURE__ */ jsx("button", { type: "submit", className: cn(buttonVariants({ size: "lg" }), "min-h-11 self-start px-8"), children: submitLabel || "Send" })
624
- ]
625
- }
626
- );
627
- }
628
- var schema5 = z.object({
629
- heading: Line(90),
630
- body: Copy(400).optional(),
631
- /** The form's slug, which the host's forms resolver knows. */
632
- form: z.string().regex(/^[a-z0-9][a-z0-9-]{0,79}$/, "A form slug is lowercase letters, digits and hyphens."),
633
- surface: Surface.default("base"),
634
- anchor: Anchor.default("contact")
635
- });
636
- function formDrawMode(resolved) {
637
- if (!resolved) return null;
638
- if (resolved.kind === "embed") return "embed";
639
- const parsed = parseForm(resolved.definition);
640
- if (!parsed.ok) return null;
641
- return isLightForm(parsed.form) ? "light" : "island";
642
- }
643
- function EmbeddedForm({ slug, heading, ctx }) {
644
- const resolved = ctx.forms.get(slug);
645
- if (!resolved) {
646
- console.error(`[genesis-sites] the form "${slug}" on ${ctx.pagePath} is unknown to the host's forms resolver; the form embed renders nothing.`);
647
- return ctx.mode === "preview" ? /* @__PURE__ */ jsxs("p", { role: "status", className: "rounded-lg border border-dashed border-border p-6 text-center", children: [
648
- "The form \u201C",
649
- slug,
650
- "\u201D isn't available. Check its slug, or that the app passes a forms resolver."
651
- ] }) : null;
652
- }
653
- if (resolved.kind === "embed") {
654
- if (!isSafeHref(resolved.url) || !resolved.url.startsWith("https://")) {
655
- console.error(`[genesis-sites] refused the form embed URL for "${slug}": it must be https.`);
656
- return null;
657
- }
658
- return /* @__PURE__ */ jsx(
659
- "iframe",
660
- {
661
- src: resolved.url,
662
- title: resolved.title || heading,
663
- loading: "lazy",
664
- referrerPolicy: "strict-origin-when-cross-origin",
665
- sandbox: "allow-forms allow-scripts allow-same-origin allow-popups",
666
- style: { height: `${Math.min(Math.max(resolved.height ?? 640, 200), 4e3)}px` },
667
- className: "w-full rounded-xl border border-border bg-background"
668
- }
669
- );
670
- }
671
- const parsed = parseForm(resolved.definition);
672
- if (!parsed.ok) {
673
- console.error(`[genesis-sites] the form "${slug}" isn't a valid genesis-forms definition: ${parsed.issues.map((i) => `${i.path} ${i.message}`).join("; ")}`);
674
- return null;
675
- }
676
- if (!isSafeHref(resolved.submitUrl) || !(resolved.submitUrl.startsWith("/") || resolved.submitUrl.startsWith("https://"))) {
677
- console.error(`[genesis-sites] the form "${slug}" has no usable submitUrl (a site path or an https URL).`);
678
- return null;
679
- }
680
- const submitUrl = resolved.submitUrl.startsWith("/") ? ctx.href(resolved.submitUrl) : resolved.submitUrl;
681
- if (isLightForm(parsed.form)) {
682
- return /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-border bg-background p-4 text-foreground sm:p-6", children: /* @__PURE__ */ jsx(LightForm, { form: parsed.form, slug, submitUrl, idPrefix: ctx.idPrefix, ...resolved.submitLabel ? { submitLabel: resolved.submitLabel } : {} }) });
683
- }
684
- return (
685
- // The island: the islands script loads the form bundle only on pages that have one,
686
- // reads the definition from the JSON beside it and makes the server-rendered form live.
687
- /* @__PURE__ */ jsxs(
688
- "div",
689
- {
690
- "data-gs-island": "form",
691
- "data-gs-form": slug,
692
- "data-gs-submit": submitUrl,
693
- ...resolved.layout ? { "data-gs-layout": resolved.layout } : {},
694
- ...resolved.submitLabel ? { "data-gs-submit-label": resolved.submitLabel } : {},
695
- className: "rounded-xl border border-border bg-background p-4 text-foreground sm:p-6",
696
- children: [
697
- /* @__PURE__ */ jsx("div", { "data-gs-form-root": "", children: /* @__PURE__ */ jsx(Form, { form: parsed.form, layout: resolved.layout ?? "classic", presentation: "embedded", slots: siteFormSlots, ...resolved.submitLabel ? { submitLabel: resolved.submitLabel } : {} }) }),
698
- /* @__PURE__ */ jsx("script", { type: "application/json", "data-gs-form-definition": "", dangerouslySetInnerHTML: { __html: safeJsonForHtml(parsed.form) } }),
699
- /* @__PURE__ */ jsx("noscript", { children: /* @__PURE__ */ jsx("p", { className: "mt-4 text-sm", children: "This form needs JavaScript. Turn it on to send it." }) })
700
- ]
701
- }
702
- )
703
- );
704
- }
705
- var formEmbedSection = defineSection({
706
- type: "form-embed",
707
- version: 1,
708
- label: "Form",
709
- description: "A genesis-forms form (by slug, through the app's forms resolver) or a hosted form in a frame.",
710
- category: "conversion",
711
- schema: schema5,
712
- variants: [
713
- { id: "card", label: "Form in a card" },
714
- { id: "split", label: "Copy left, form right" }
715
- ],
716
- defaultVariant: "card",
717
- presets: [
718
- { name: "Contact form", variant: "split", props: { heading: "Get in touch", body: "Tell us a little about yourself.", form: "contact", anchor: "contact" } },
719
- { name: "Form card", variant: "card", props: { heading: "Book a free session", form: "contact", anchor: "contact" } }
720
- ],
721
- render: ({ props, variant, node, ctx }) => {
722
- const inverted = onAccent(props.surface);
723
- const copy = /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-4", variant === "card" && "items-center text-center"), children: [
724
- /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size: "lg", children: props.heading }),
725
- props.body ? /* @__PURE__ */ jsx(BandLead, { tone: inverted ? "inherit" : "muted", children: props.body }) : null
726
- ] });
727
- if (variant === "split") {
728
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: /* @__PURE__ */ jsxs("div", { className: "grid gap-10 md:grid-cols-2 md:gap-12", children: [
729
- copy,
730
- /* @__PURE__ */ jsx(EmbeddedForm, { slug: props.form, heading: props.heading, ctx })
731
- ] }) });
732
- }
733
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, width: "sm", children: [
734
- copy,
735
- /* @__PURE__ */ jsx("div", { className: "mt-8", children: /* @__PURE__ */ jsx(EmbeddedForm, { slug: props.form, heading: props.heading, ctx }) })
736
- ] });
737
- }
738
- });
739
- var schema6 = z.object({
740
- heading: Line(90).optional(),
741
- body: Copy(320).optional(),
742
- images: z.array(Image.extend({ caption: Line(140).optional() })).min(1).max(24),
743
- surface: Surface.default("base"),
744
- anchor: Anchor
745
- });
746
- var gallerySection = defineSection({
747
- type: "gallery",
748
- version: 1,
749
- label: "Gallery",
750
- description: "Photos in an even grid or a masonry layout, each with alt text.",
751
- category: "content",
752
- schema: schema6,
753
- variants: [
754
- { id: "grid", label: "Even grid" },
755
- { id: "masonry", label: "Masonry" }
756
- ],
757
- defaultVariant: "grid",
758
- presets: [
759
- {
760
- name: "Photo grid",
761
- variant: "grid",
762
- props: { heading: "Inside", images: [{ src: "/photo-1.jpg", alt: "Describe the photo" }, { src: "/photo-2.jpg", alt: "Describe the photo" }, { src: "/photo-3.jpg", alt: "Describe the photo" }] }
763
- }
764
- ],
765
- render: ({ props, variant, node, ctx }) => {
766
- const intro = props.heading ? /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, body: props.body, tone: onAccent(props.surface) ? "inherit" : "muted" }) : /* @__PURE__ */ jsx(HiddenTitle, { ctx, children: "Gallery" });
767
- const masonry = variant === "masonry";
768
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
769
- intro,
770
- /* @__PURE__ */ jsx("ul", { className: masonry ? "mt-10 columns-1 gap-4 sm:columns-2 lg:columns-3 [&>li]:mb-4 [&>li]:break-inside-avoid" : "mt-10 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: props.images.map((image, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("figure", { className: "flex flex-col gap-2", children: [
771
- /* @__PURE__ */ jsx(
772
- SiteImage,
773
- {
774
- image,
775
- ctx,
776
- sizes: "(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw",
777
- className: masonry ? "w-full rounded-lg" : "aspect-square w-full rounded-lg object-cover"
778
- }
779
- ),
780
- image.caption ? /* @__PURE__ */ jsx("figcaption", { className: "text-sm", children: image.caption }) : null
781
- ] }) }, i)) })
782
- ] });
783
- }
784
- });
785
- var schema7 = z.object({
786
- eyebrow: Line(60).optional(),
787
- heading: Line(90),
788
- body: Copy(320).optional(),
789
- primaryCta: Link.optional(),
790
- secondaryCta: Link.optional(),
791
- image: Image.optional(),
792
- anchor: Anchor
793
- });
794
- var heroSection = defineSection({
795
- type: "hero",
796
- version: 1,
797
- label: "Hero",
798
- description: "The page's opening: its main heading, a line of copy and up to two buttons.",
799
- category: "intro",
800
- limit: 1,
801
- providesH1: true,
802
- schema: schema7,
803
- variants: [
804
- { id: "centered", label: "Centred" },
805
- { id: "split", label: "Text and image side by side" },
806
- { id: "image-bg", label: "Text over an image" }
807
- ],
808
- defaultVariant: "centered",
809
- presets: [
810
- {
811
- name: "Centred hero",
812
- variant: "centered",
813
- props: { heading: "Say what you do, for whom", body: "One or two sentences on why it matters.", primaryCta: { label: "Get started", href: "#contact" } }
814
- },
815
- {
816
- name: "Hero with image",
817
- variant: "split",
818
- props: { heading: "Say what you do, for whom", body: "One or two sentences on why it matters.", primaryCta: { label: "Get started", href: "#contact" } }
819
- }
820
- ],
821
- render: ({ props, variant, node, ctx }) => {
822
- const intro = (align, tone = "muted") => /* @__PURE__ */ jsxs("div", { className: align === "center" ? "mx-auto flex max-w-3xl flex-col items-center gap-6 text-center" : "flex flex-col items-start gap-6", children: [
823
- props.eyebrow ? /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold uppercase tracking-wider", children: props.eyebrow }) : null,
824
- /* @__PURE__ */ jsx(SectionTitle, { ctx, id: titleId(ctx), size: "xl", children: props.heading }),
825
- props.body ? /* @__PURE__ */ jsx(BandLead, { tone, children: props.body }) : null,
826
- /* @__PURE__ */ jsx(Actions, { primary: props.primaryCta, secondary: props.secondaryCta, ctx, onAccent: tone === "inherit", className: align === "center" ? "sm:justify-center" : void 0 })
827
- ] });
828
- if (variant === "image-bg" && props.image) {
829
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: "inverse", spacing: "lg", className: "isolate overflow-hidden", children: [
830
- /* @__PURE__ */ jsx(SiteImage, { image: { ...props.image, decorative: true }, ctx, priority: true, sizes: "100vw", className: "absolute inset-0 -z-10 h-full w-full object-cover" }),
831
- /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "absolute inset-0 -z-10 bg-foreground/80" }),
832
- intro("center", "inherit")
833
- ] });
834
- }
835
- if (variant === "split") {
836
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, spacing: "lg", children: /* @__PURE__ */ jsxs("div", { className: "grid items-center gap-10 md:grid-cols-2 md:gap-12", children: [
837
- intro("left"),
838
- props.image ? /* @__PURE__ */ jsx(SiteImage, { image: props.image, ctx, priority: true, sizes: "(min-width: 768px) 50vw, 100vw", className: "aspect-[4/3] w-full rounded-xl object-cover" }) : /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "hidden aspect-[4/3] w-full rounded-xl bg-muted md:block" })
839
- ] }) });
840
- }
841
- return /* @__PURE__ */ jsx(SectionBand, { node, ctx, anchor: props.anchor, spacing: "lg", children: intro("center") });
842
- }
843
- });
844
- var schema8 = z.object({
845
- heading: Line(90).optional(),
846
- items: z.array(
847
- z.object({
848
- /** The organisation's name: the logo's alt text when the logo has none. */
849
- name: Line(80),
850
- image: Image,
851
- href: Href.optional()
852
- })
853
- ).min(1).max(24),
854
- surface: Surface.default("base"),
855
- anchor: Anchor
856
- });
857
- var logosSection = defineSection({
858
- type: "logos",
859
- version: 1,
860
- label: "Logos",
861
- description: "Partners, clients or press, as a row or grid of logos.",
862
- category: "social-proof",
863
- schema: schema8,
864
- variants: [
865
- { id: "row", label: "One row" },
866
- { id: "grid", label: "Grid" }
867
- ],
868
- defaultVariant: "row",
869
- presets: [
870
- {
871
- name: "Logo row",
872
- variant: "row",
873
- props: { heading: "Trusted by", items: [{ name: "Partner", image: { src: "/logo-partner.svg", alt: "Partner" } }] }
874
- }
875
- ],
876
- render: ({ props, variant, node, ctx }) => {
877
- const intro = props.heading ? /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, size: "md", tone: onAccent(props.surface) ? "inherit" : "muted" }) : /* @__PURE__ */ jsx(HiddenTitle, { ctx, children: "Logos" });
878
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, spacing: "sm", children: [
879
- intro,
880
- /* @__PURE__ */ jsx("ul", { className: cn("items-center", props.heading && "mt-8", variant === "grid" ? "grid grid-cols-2 gap-8 sm:grid-cols-3 lg:grid-cols-4" : "flex flex-wrap justify-center gap-x-10 gap-y-6"), children: props.items.map((item, i) => {
881
- const img = /* @__PURE__ */ jsx(SiteImage, { image: { ...item.image, alt: item.image.alt || item.name, decorative: false }, ctx, sizes: "160px", className: "mx-auto h-10 w-auto max-w-40 object-contain" });
882
- return /* @__PURE__ */ jsx("li", { className: "flex justify-center", children: item.href ? /* @__PURE__ */ jsx("a", { href: resolveHref(item.href, ctx), className: "inline-flex min-h-11 items-center rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", children: img }) : img }, i);
883
- }) })
884
- ] });
885
- }
886
- });
887
- var schema9 = z.object({
888
- /** Replaces the brand's logo in the header. */
889
- logo: Image.optional(),
890
- /** Show the site's name beside the logo (always shown when there is no logo). */
891
- showName: z.boolean().default(true),
892
- cta: Link.optional()
893
- });
894
- function isCurrent(item, ctx) {
895
- return item.href === ctx.pagePath;
896
- }
897
- function Brand({ props, ctx }) {
898
- const logo = props.logo ?? (ctx.site.logo ? { src: ctx.site.logo.src, alt: ctx.site.logo.alt, decorative: false } : void 0);
899
- const showName = props.showName || !logo;
900
- return /* @__PURE__ */ jsxs(
901
- "a",
902
- {
903
- href: ctx.href("/"),
904
- className: "inline-flex min-h-11 items-center gap-3 rounded-md font-display text-lg font-bold tracking-tight focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
905
- children: [
906
- logo ? /* @__PURE__ */ jsx(SiteImage, { image: { ...logo, alt: showName ? "" : logo.alt || ctx.site.name, decorative: showName }, ctx, priority: true, className: "h-8 w-auto" }) : null,
907
- showName ? /* @__PURE__ */ jsx("span", { children: ctx.site.name }) : null,
908
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: ", home" })
909
- ]
910
- }
911
- );
912
- }
913
- function Items({ ctx, className, itemClassName }) {
914
- return /* @__PURE__ */ jsx("ul", { className, children: ctx.site.nav.map((item) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
915
- "a",
916
- {
917
- href: resolveHref(item.href, ctx),
918
- ...isCurrent(item, ctx) ? { "aria-current": "page" } : {},
919
- className: cn(
920
- "inline-flex min-h-11 items-center rounded-md px-3 text-sm font-medium hover:bg-muted focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring aria-[current=page]:underline aria-[current=page]:underline-offset-8",
921
- itemClassName
922
- ),
923
- children: item.label
924
- }
925
- ) }, `${item.label}-${item.href}`)) });
926
- }
927
- function NavSection({ props, variant, node, ctx }) {
928
- const attrs = bandAttrs(node, ctx, void 0, false);
929
- const hasMenu = variant !== "minimal" && ctx.site.nav.length > 0;
930
- const centered = variant === "centered";
931
- return /* @__PURE__ */ jsx(Band, { ...attrs, as: "header", spacing: "none", className: cn("border-b border-border", attrs.className), children: /* @__PURE__ */ jsxs("div", { className: cn("relative flex min-h-16 flex-wrap items-center justify-between gap-x-4 gap-y-2 py-2", centered && "md:flex-col md:justify-center md:py-4"), children: [
932
- /* @__PURE__ */ jsx(Brand, { props, ctx }),
933
- hasMenu ? /* @__PURE__ */ jsxs("nav", { "aria-label": "Main", className: cn("flex items-center gap-2", centered && "md:w-full md:justify-center"), children: [
934
- /* @__PURE__ */ jsx(Items, { ctx, className: "hidden items-center gap-1 md:flex" }),
935
- props.cta ? /* @__PURE__ */ jsx(ButtonLink, { link: props.cta, ctx, className: "hidden md:inline-flex" }) : null,
936
- /* @__PURE__ */ jsxs("details", { className: "group md:hidden", "data-gs-island": "menu", children: [
937
- /* @__PURE__ */ jsxs("summary", { className: "flex min-h-11 min-w-11 cursor-pointer list-none items-center justify-center rounded-md border border-border focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden", children: [
938
- /* @__PURE__ */ jsx(Menu, { "aria-hidden": "true", className: "size-5" }),
939
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Menu" })
940
- ] }),
941
- /* @__PURE__ */ jsxs("div", { className: "absolute inset-x-0 top-full z-50 border-b border-border bg-background px-4 pb-4 shadow-lg", children: [
942
- /* @__PURE__ */ jsx(Items, { ctx, className: "flex flex-col py-2", itemClassName: "w-full" }),
943
- props.cta ? /* @__PURE__ */ jsx(ButtonLink, { link: props.cta, ctx, className: "w-full" }) : null
944
- ] })
945
- ] })
946
- ] }) : props.cta ? /* @__PURE__ */ jsx(ButtonLink, { link: props.cta, ctx }) : null
947
- ] }) });
948
- }
949
- var navSection = defineSection({
950
- type: "nav",
951
- version: 1,
952
- label: "Navigation",
953
- description: "The site's header: logo, menu and a button. On phones the menu folds into a button.",
954
- category: "header",
955
- placement: "header",
956
- limit: 1,
957
- schema: schema9,
958
- variants: [
959
- { id: "simple", label: "Logo left, menu right" },
960
- { id: "centered", label: "Logo centred, menu below" },
961
- { id: "minimal", label: "Logo and button only" }
962
- ],
963
- defaultVariant: "simple",
964
- presets: [
965
- { name: "Header with menu", variant: "simple", props: { showName: true, cta: { label: "Get started", href: "#contact" } } },
966
- { name: "Centred header", variant: "centered", props: { showName: true } },
967
- { name: "Landing page header", variant: "minimal", props: { showName: true, cta: { label: "Get started", href: "#contact" } } }
968
- ],
969
- render: (input) => /* @__PURE__ */ jsx(NavSection, { ...input })
970
- });
971
- var Tier = z.object({
972
- name: Line(40),
973
- price: Line(20),
974
- /** "per month", "per session". */
975
- period: Line(30).optional(),
976
- description: Copy(200).optional(),
977
- features: z.array(Line(100)).max(12).default([]),
978
- cta: Link.optional(),
979
- highlighted: z.boolean().optional()
980
- });
981
- var schema10 = z.object({
982
- heading: Line(90),
983
- body: Copy(320).optional(),
984
- tiers: z.array(Tier).min(1).max(4),
985
- note: Copy(300).optional(),
986
- surface: Surface.default("base"),
987
- anchor: Anchor
988
- });
989
- var COLS = { 1: "max-w-md mx-auto", 2: "md:grid-cols-2 max-w-4xl mx-auto", 3: "md:grid-cols-3", 4: "md:grid-cols-2 lg:grid-cols-4" };
990
- var pricingSection = defineSection({
991
- type: "pricing",
992
- version: 1,
993
- label: "Pricing",
994
- description: "Up to four plans with a price, what's included and a button.",
995
- category: "conversion",
996
- schema: schema10,
997
- variants: [
998
- { id: "cards", label: "Plan cards" },
999
- { id: "compact", label: "Compact list" }
1000
- ],
1001
- defaultVariant: "cards",
1002
- presets: [
1003
- {
1004
- name: "Three plans",
1005
- variant: "cards",
1006
- props: {
1007
- heading: "Plans",
1008
- tiers: [
1009
- { name: "Starter", price: "$29", period: "per month", features: ["One thing", "Another thing"], cta: { label: "Choose Starter", href: "#contact" } },
1010
- { name: "Regular", price: "$59", period: "per month", features: ["Everything in Starter", "More"], cta: { label: "Choose Regular", href: "#contact" }, highlighted: true },
1011
- { name: "Unlimited", price: "$99", period: "per month", features: ["Everything", "Most"], cta: { label: "Choose Unlimited", href: "#contact" } }
1012
- ]
1013
- }
1014
- }
1015
- ],
1016
- render: ({ props, variant, node, ctx }) => {
1017
- const inverted = onAccent(props.surface);
1018
- const intro = /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, body: props.body, tone: inverted ? "inherit" : "muted" });
1019
- const note = props.note ? /* @__PURE__ */ jsx("p", { className: cn("mt-8 text-center text-sm", !inverted && "text-muted-foreground"), children: props.note }) : null;
1020
- if (variant === "compact") {
1021
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, width: "md", children: [
1022
- intro,
1023
- /* @__PURE__ */ jsx("ul", { className: "mx-auto mt-10 flex max-w-3xl flex-col divide-y divide-border rounded-xl border border-border bg-background text-foreground", children: props.tiers.map((tier, i) => /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-4 p-6 sm:flex-row sm:items-center sm:justify-between", children: [
1024
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
1025
- /* @__PURE__ */ jsx(ItemTitle, { ctx, children: tier.name }),
1026
- tier.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: tier.description }) : null
1027
- ] }),
1028
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
1029
- /* @__PURE__ */ jsxs("p", { children: [
1030
- /* @__PURE__ */ jsx("span", { className: "font-display text-2xl font-bold", children: tier.price }),
1031
- tier.period ? /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
1032
- " ",
1033
- tier.period
1034
- ] }) : null
1035
- ] }),
1036
- tier.cta ? /* @__PURE__ */ jsx(ButtonLink, { link: tier.cta, ctx, tone: tier.highlighted ? "primary" : "outline" }) : null
1037
- ] })
1038
- ] }, i)) }),
1039
- note
1040
- ] });
1041
- }
1042
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
1043
- intro,
1044
- /* @__PURE__ */ jsx("ul", { className: cn("mt-12 grid gap-6", COLS[props.tiers.length]), children: props.tiers.map((tier, i) => /* @__PURE__ */ jsxs(
1045
- "li",
1046
- {
1047
- className: cn(
1048
- "flex flex-col gap-6 rounded-2xl border bg-background p-8 text-foreground",
1049
- tier.highlighted ? "border-2 border-primary shadow-lg" : "border-border"
1050
- ),
1051
- children: [
1052
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
1053
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1054
- /* @__PURE__ */ jsx(ItemTitle, { ctx, children: tier.name }),
1055
- tier.highlighted ? /* @__PURE__ */ jsx("span", { className: "rounded-full bg-primary px-3 py-1 text-xs font-semibold text-primary-foreground", children: "Most popular" }) : null
1056
- ] }),
1057
- tier.description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: tier.description }) : null
1058
- ] }),
1059
- /* @__PURE__ */ jsxs("p", { children: [
1060
- /* @__PURE__ */ jsx("span", { className: "font-display text-4xl font-bold", children: tier.price }),
1061
- tier.period ? /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1062
- " ",
1063
- tier.period
1064
- ] }) : null
1065
- ] }),
1066
- tier.features.length ? /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-3", children: tier.features.map((f, j) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3", children: [
1067
- /* @__PURE__ */ jsx(Check, { "aria-hidden": "true", className: "mt-0.5 size-5 shrink-0" }),
1068
- /* @__PURE__ */ jsx("span", { children: f })
1069
- ] }, j)) }) : null,
1070
- tier.cta ? /* @__PURE__ */ jsx(ButtonLink, { link: tier.cta, ctx, tone: tier.highlighted ? "primary" : "outline", className: "mt-auto w-full" }) : null
1071
- ]
1072
- },
1073
- i
1074
- )) }),
1075
- note
1076
- ] });
1077
- }
1078
- });
1079
- var schema11 = z.object({
1080
- heading: Line(90).optional(),
1081
- body: RichText,
1082
- surface: Surface.default("base"),
1083
- anchor: Anchor
1084
- });
1085
- var richTextSection = defineSection({
1086
- type: "rich-text",
1087
- version: 1,
1088
- label: "Text",
1089
- description: "Long copy with headings, lists, links and quotes. Sanitised when it renders.",
1090
- category: "content",
1091
- schema: schema11,
1092
- variants: [
1093
- { id: "narrow", label: "Reading width" },
1094
- { id: "wide", label: "Wide" }
1095
- ],
1096
- defaultVariant: "narrow",
1097
- presets: [
1098
- {
1099
- name: "Text",
1100
- variant: "narrow",
1101
- props: {
1102
- body: {
1103
- type: "doc",
1104
- content: [
1105
- { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "A heading" }] },
1106
- { type: "paragraph", content: [{ type: "text", text: "Write here." }] }
1107
- ]
1108
- }
1109
- }
1110
- }
1111
- ],
1112
- render: ({ props, variant, node, ctx }) => {
1113
- const intro = props.heading ? /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, align: "left", tone: onAccent(props.surface) ? "inherit" : "muted" }) : null;
1114
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, width: variant === "wide" ? "lg" : "prose", labelled: Boolean(props.heading), children: [
1115
- intro,
1116
- /* @__PURE__ */ jsx(
1117
- RichText$1,
1118
- {
1119
- value: props.body,
1120
- headingLevels: props.heading ? [3, 4] : [2, 3, 4],
1121
- className: cn(richTextClassName, "text-base sm:text-lg [&_h2]:font-display [&_h2]:text-2xl [&_h3]:font-display [&_h3]:text-xl", props.heading && "mt-6")
1122
- }
1123
- )
1124
- ] });
1125
- }
1126
- });
1127
- var schema12 = z.object({
1128
- heading: Line(90).optional(),
1129
- body: Copy(320).optional(),
1130
- items: z.array(z.object({ value: Line(16), label: Line(60) })).min(1).max(6),
1131
- surface: Surface.default("muted"),
1132
- anchor: Anchor
1133
- });
1134
- var COLS2 = { 1: "", 2: "sm:grid-cols-2", 3: "sm:grid-cols-3", 4: "sm:grid-cols-2 lg:grid-cols-4", 5: "sm:grid-cols-3 lg:grid-cols-5", 6: "sm:grid-cols-3 lg:grid-cols-6" };
1135
- var statsSection = defineSection({
1136
- type: "stats",
1137
- version: 1,
1138
- label: "Stats",
1139
- description: "A few numbers that matter, each with a label.",
1140
- category: "social-proof",
1141
- schema: schema12,
1142
- variants: [
1143
- { id: "row", label: "In a row" },
1144
- { id: "cards", label: "Cards" }
1145
- ],
1146
- defaultVariant: "row",
1147
- presets: [
1148
- {
1149
- name: "Three numbers",
1150
- variant: "row",
1151
- props: { items: [{ value: "12", label: "years open" }, { value: "40+", label: "classes a week" }, { value: "6", label: "coaches" }] }
1152
- }
1153
- ],
1154
- render: ({ props, variant, node, ctx }) => {
1155
- const inverted = onAccent(props.surface);
1156
- const intro = props.heading ? /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, body: props.body, tone: inverted ? "inherit" : "muted" }) : /* @__PURE__ */ jsx(HiddenTitle, { ctx, children: "In numbers" });
1157
- const cards = variant === "cards";
1158
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
1159
- intro,
1160
- /* @__PURE__ */ jsx("dl", { className: cn("grid gap-6 text-center", COLS2[props.items.length], props.heading && "mt-10"), children: props.items.map((item, i) => /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col-reverse gap-1", cards && "rounded-xl border border-border bg-background p-6 text-foreground"), children: [
1161
- /* @__PURE__ */ jsx("dt", { className: cn("text-sm font-medium", !inverted || cards ? "text-muted-foreground" : void 0), children: item.label }),
1162
- /* @__PURE__ */ jsx("dd", { className: "font-display text-4xl font-bold tracking-tight sm:text-5xl", children: item.value })
1163
- ] }, i)) })
1164
- ] });
1165
- }
1166
- });
1167
- var Quote = z.object({
1168
- quote: Copy(500).min(1),
1169
- name: Line(80),
1170
- role: Line(80).optional(),
1171
- avatar: Image.optional()
1172
- });
1173
- var schema13 = z.object({
1174
- heading: Line(90).optional(),
1175
- body: Copy(320).optional(),
1176
- items: z.array(Quote).min(1).max(12),
1177
- surface: Surface.default("muted"),
1178
- anchor: Anchor
1179
- });
1180
- var testimonialsSection = defineSection({
1181
- type: "testimonials",
1182
- version: 1,
1183
- label: "Testimonials",
1184
- description: "Quotes from real people, with their permission.",
1185
- category: "social-proof",
1186
- schema: schema13,
1187
- variants: [
1188
- { id: "grid", label: "Quotes in a grid" },
1189
- { id: "single", label: "One large quote" }
1190
- ],
1191
- defaultVariant: "grid",
1192
- presets: [
1193
- { name: "Quotes", variant: "grid", props: { heading: "What people say", items: [{ quote: "A quote, in their words.", name: "Their name" }] } },
1194
- { name: "One quote", variant: "single", props: { items: [{ quote: "A quote, in their words.", name: "Their name", role: "Who they are" }] } }
1195
- ],
1196
- render: ({ props, variant, node, ctx }) => {
1197
- const inverted = onAccent(props.surface);
1198
- const muted = inverted ? void 0 : "text-muted-foreground";
1199
- const person = (item, large = false) => /* @__PURE__ */ jsxs("figcaption", { className: "flex items-center gap-3", children: [
1200
- item.avatar ? /* @__PURE__ */ jsx(SiteImage, { image: item.avatar, ctx, sizes: "48px", className: cn("rounded-full object-cover", large ? "size-14" : "size-11") }) : null,
1201
- /* @__PURE__ */ jsxs("span", { className: "flex flex-col text-left", children: [
1202
- /* @__PURE__ */ jsx("span", { className: "font-semibold", children: item.name }),
1203
- item.role ? /* @__PURE__ */ jsx("span", { className: cn("text-sm", muted), children: item.role }) : null
1204
- ] })
1205
- ] });
1206
- const intro = props.heading ? /* @__PURE__ */ jsx(SectionIntro, { ctx, heading: props.heading, body: props.body, tone: inverted ? "inherit" : "muted" }) : /* @__PURE__ */ jsx(HiddenTitle, { ctx, children: "Testimonials" });
1207
- if (variant === "single") {
1208
- const item = props.items[0];
1209
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, width: "md", children: [
1210
- intro,
1211
- /* @__PURE__ */ jsxs("figure", { className: cn("mx-auto flex max-w-3xl flex-col items-center gap-8 text-center", props.heading && "mt-10"), children: [
1212
- /* @__PURE__ */ jsx("blockquote", { className: "font-display text-2xl font-medium leading-snug text-balance sm:text-3xl", children: /* @__PURE__ */ jsxs("p", { children: [
1213
- "\u201C",
1214
- item.quote,
1215
- "\u201D"
1216
- ] }) }),
1217
- person(item, true)
1218
- ] })
1219
- ] });
1220
- }
1221
- return /* @__PURE__ */ jsxs(SectionBand, { node, ctx, anchor: props.anchor, surface: props.surface, children: [
1222
- intro,
1223
- /* @__PURE__ */ jsx("ul", { className: cn("grid gap-6 sm:grid-cols-2 lg:grid-cols-3", props.heading && "mt-12"), children: props.items.map((item, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("figure", { className: "flex h-full flex-col justify-between gap-6 rounded-xl border border-border bg-background p-6 text-foreground", children: [
1224
- /* @__PURE__ */ jsx("blockquote", { className: "leading-relaxed", children: /* @__PURE__ */ jsxs("p", { children: [
1225
- "\u201C",
1226
- item.quote,
1227
- "\u201D"
1228
- ] }) }),
1229
- person(item)
1230
- ] }) }, i)) })
1231
- ] });
1232
- }
1233
- });
1234
-
1235
- // src/sections/index.ts
1236
- var builtInSections = Object.freeze([
1237
- navSection,
1238
- heroSection,
1239
- featuresSection,
1240
- ctaSection,
1241
- testimonialsSection,
1242
- faqSection,
1243
- pricingSection,
1244
- logosSection,
1245
- statsSection,
1246
- gallerySection,
1247
- richTextSection,
1248
- formEmbedSection,
1249
- footerSection
1250
- ]);
1251
- for (const def of builtInSections) registerBuiltInSection(def);
1252
-
1253
- export { Anchor, ButtonLink, HiddenTitle, ICONS, IconName, ItemTitle, SectionBand, SectionIntro, SectionTitle, SiteFormHeader, SiteImage, TextLink, builtInSections, ctaSection, faqSection, featuresSection, footerSection, formDrawMode, formEmbedSection, gallerySection, heroSection, logosSection, navSection, pricingSection, resolveHref, richTextSection, siteFormSlots, statsSection, testimonialsSection, titleId };
1254
- //# sourceMappingURL=chunk-43XUAO63.js.map
1255
- //# sourceMappingURL=chunk-43XUAO63.js.map