create-vexcms 0.1.0-alpha.2 → 0.1.0-alpha.21

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 (115) hide show
  1. package/README.md +48 -14
  2. package/dist/index.js +1714 -1504
  3. package/package.json +2 -2
  4. package/templates/base-nextjs/README.md +18 -5
  5. package/templates/base-nextjs/convex/auth/index.ts +2 -2
  6. package/templates/base-nextjs/convex/auth/options.ts +27 -26
  7. package/templates/base-nextjs/convex/auth/schema.ts +51 -0
  8. package/templates/base-nextjs/convex/auth/sessions.ts +3 -3
  9. package/templates/base-nextjs/convex/tsconfig.json +2 -2
  10. package/templates/base-nextjs/convex/vex/firstUser.ts +2 -2
  11. package/templates/base-nextjs/convex/vex/globals.ts +3 -2
  12. package/templates/base-nextjs/convex/vex/media.ts +3 -2
  13. package/templates/base-nextjs/convex/vex.schema.ts +1 -0
  14. package/templates/base-nextjs/convex/vex.ts +17 -4
  15. package/templates/base-nextjs/next.config.ts +52 -6
  16. package/templates/base-nextjs/package.json +18 -7
  17. package/templates/base-nextjs/public/favicons/favicon.ico +0 -0
  18. package/templates/base-nextjs/src/app/(frontend)/@auth/(...)auth/[pathname]/view.tsx +27 -15
  19. package/templates/base-nextjs/src/app/(vexcms)/admin/[[...path]]/page.tsx +2 -2
  20. package/templates/base-nextjs/src/app/(vexcms)/admin/clientProviders.tsx +9 -14
  21. package/templates/base-nextjs/src/app/(vexcms)/admin/layout.tsx +8 -6
  22. package/templates/base-nextjs/src/app/api/vex/revalidate/route.ts +19 -0
  23. package/templates/base-nextjs/src/app/globals.css +10 -9
  24. package/templates/base-nextjs/src/app/layout.tsx +6 -0
  25. package/templates/base-nextjs/src/auth/access.ts +14 -2
  26. package/templates/base-nextjs/src/auth/serverUtils.ts +20 -6
  27. package/templates/base-nextjs/src/components/WelcomePage.tsx +54 -43
  28. package/templates/base-nextjs/src/components/component-example.tsx +1 -1
  29. package/templates/base-nextjs/src/components/providers/server.tsx +16 -8
  30. package/templates/base-nextjs/src/vex.config.server.ts +30 -0
  31. package/templates/base-nextjs/src/vex.config.ts +14 -11
  32. package/templates/base-nextjs/src/vex.types.ts +2 -1
  33. package/templates/base-nextjs/src/vexcms/collections/images.ts +1 -1
  34. package/templates/base-nextjs/src/vexcms/collections/users.ts +3 -1
  35. package/templates/marketing-site/convex/pages.ts +20 -1
  36. package/templates/marketing-site/convex/schema.ts +4 -4
  37. package/templates/marketing-site/convex/seed.ts +515 -70
  38. package/templates/marketing-site/convex/siteSettings.ts +1 -1
  39. package/templates/marketing-site/convex/theme.ts +3 -3
  40. package/templates/marketing-site/convex/vex.schema.ts +75 -38
  41. package/templates/marketing-site/public/favicon.svg +14 -0
  42. package/templates/marketing-site/public/favicons/apple-touch-icon.png +0 -0
  43. package/templates/marketing-site/public/favicons/favicon.ico +0 -0
  44. package/templates/marketing-site/src/app/(frontend)/(site)/PageContent.tsx +64 -0
  45. package/templates/marketing-site/src/app/(frontend)/(site)/SiteLivePreviewProvider.tsx +31 -0
  46. package/templates/marketing-site/src/app/(frontend)/(site)/[slug]/page.tsx +63 -0
  47. package/templates/marketing-site/src/app/(frontend)/(site)/layout.tsx +50 -0
  48. package/templates/marketing-site/src/app/(frontend)/(site)/page.tsx +33 -0
  49. package/templates/marketing-site/src/app/(frontend)/layout.tsx +10 -24
  50. package/templates/marketing-site/src/app/(vexcms)/admin/layout.tsx +32 -0
  51. package/templates/marketing-site/src/app/globals.css +413 -0
  52. package/templates/marketing-site/src/app/layout.tsx +85 -0
  53. package/templates/marketing-site/src/app/robots.ts +13 -0
  54. package/templates/marketing-site/src/app/sitemap.ts +18 -0
  55. package/templates/marketing-site/src/components/BrandMark.tsx +27 -0
  56. package/templates/marketing-site/src/components/CodeHighlightContext.tsx +76 -0
  57. package/templates/marketing-site/src/components/CodePane.tsx +98 -0
  58. package/templates/marketing-site/src/components/Container.tsx +25 -0
  59. package/templates/marketing-site/src/components/CopyButton.tsx +80 -0
  60. package/templates/marketing-site/src/components/FirstAdminBootstrap.tsx +54 -0
  61. package/templates/marketing-site/src/components/InstallCommand.tsx +29 -0
  62. package/templates/marketing-site/src/components/MediaImage.tsx +59 -0
  63. package/templates/marketing-site/src/components/SectionHeader.tsx +51 -0
  64. package/templates/marketing-site/src/components/SiteFooter.tsx +8 -6
  65. package/templates/marketing-site/src/components/SiteHeader.tsx +8 -6
  66. package/templates/marketing-site/src/components/ThemeLive.tsx +1 -2
  67. package/templates/marketing-site/src/components/ThemeStyle.tsx +3 -3
  68. package/templates/marketing-site/src/components/motion-primitives/animated-group.tsx +44 -120
  69. package/templates/marketing-site/src/components/motion-primitives/reveal.module.css +32 -0
  70. package/templates/marketing-site/src/components/motion-primitives/text-effect.tsx +47 -266
  71. package/templates/marketing-site/src/lib/codeHighlightKey.ts +27 -0
  72. package/templates/marketing-site/src/lib/highlight.ts +134 -0
  73. package/templates/marketing-site/src/lib/highlightClient.ts +37 -0
  74. package/templates/marketing-site/src/lib/metadata.ts +61 -33
  75. package/templates/marketing-site/src/lib/resolvePagePath.ts +8 -0
  76. package/templates/marketing-site/src/lib/vex.ts +18 -0
  77. package/templates/marketing-site/src/proxy.ts +179 -0
  78. package/templates/marketing-site/src/vex.config.server.ts +27 -0
  79. package/templates/marketing-site/src/vex.config.ts +27 -9
  80. package/templates/marketing-site/src/vex.types.ts +102 -76
  81. package/templates/marketing-site/src/vexcms/api.ts +2 -2
  82. package/templates/marketing-site/src/vexcms/blocks/CTA/config.ts +3 -2
  83. package/templates/marketing-site/src/vexcms/blocks/CTA/index.tsx +40 -24
  84. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/config.ts +62 -0
  85. package/templates/marketing-site/src/vexcms/blocks/CodeShowcase/index.tsx +106 -0
  86. package/templates/marketing-site/src/vexcms/blocks/FAQ/config.ts +4 -3
  87. package/templates/marketing-site/src/vexcms/blocks/FAQ/index.tsx +63 -50
  88. package/templates/marketing-site/src/vexcms/blocks/Features/config.ts +1 -0
  89. package/templates/marketing-site/src/vexcms/blocks/Features/index.tsx +43 -58
  90. package/templates/marketing-site/src/vexcms/blocks/Footer/config.ts +6 -3
  91. package/templates/marketing-site/src/vexcms/blocks/Footer/index.tsx +90 -53
  92. package/templates/marketing-site/src/vexcms/blocks/Header/config.ts +10 -3
  93. package/templates/marketing-site/src/vexcms/blocks/Header/index.tsx +205 -138
  94. package/templates/marketing-site/src/vexcms/blocks/Hero/config.ts +27 -12
  95. package/templates/marketing-site/src/vexcms/blocks/Hero/index.tsx +174 -119
  96. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/config.ts +2 -1
  97. package/templates/marketing-site/src/vexcms/blocks/HowItWorks/index.tsx +59 -51
  98. package/templates/marketing-site/src/vexcms/blocks/Roadmap/config.ts +26 -5
  99. package/templates/marketing-site/src/vexcms/blocks/Roadmap/index.tsx +256 -73
  100. package/templates/marketing-site/src/vexcms/blocks/Split/config.ts +62 -0
  101. package/templates/marketing-site/src/vexcms/blocks/Split/index.tsx +186 -0
  102. package/templates/marketing-site/src/vexcms/blocks/Stats/config.ts +35 -0
  103. package/templates/marketing-site/src/vexcms/blocks/Stats/index.tsx +77 -0
  104. package/templates/marketing-site/src/vexcms/blocks/config.ts +27 -11
  105. package/templates/marketing-site/src/vexcms/blocks/constants.ts +3 -0
  106. package/templates/marketing-site/src/vexcms/blocks/index.ts +9 -0
  107. package/templates/marketing-site/src/vexcms/collections/footers.ts +1 -0
  108. package/templates/marketing-site/src/vexcms/collections/headers.ts +1 -0
  109. package/templates/marketing-site/src/vexcms/collections/pages.ts +23 -1
  110. package/templates/marketing-site/src/vexcms/collections/themeColors.ts +14 -14
  111. package/templates/marketing-site/src/vexcms/collections/users.ts +20 -0
  112. package/dist/index.mjs +0 -30976
  113. package/templates/marketing-site/src/app/(frontend)/PageContent.tsx +0 -53
  114. package/templates/marketing-site/src/app/(frontend)/[slug]/page.tsx +0 -25
  115. package/templates/marketing-site/src/app/(frontend)/page.tsx +0 -21
@@ -2,172 +2,239 @@
2
2
 
3
3
  import type { BlockComponentProps } from "@vexcms/react"
4
4
 
5
- import { Menu, Settings, X } from "lucide-react"
5
+ import {
6
+ buttonVariants,
7
+ cn,
8
+ Separator,
9
+ Sheet,
10
+ SheetContent,
11
+ SheetTitle,
12
+ SheetTrigger,
13
+ useCanAccessAdminPanel,
14
+ } from "@vexcms/react"
15
+ import { Menu, Settings } from "lucide-react"
6
16
  import Link from "next/link"
17
+ import { usePathname } from "next/navigation"
7
18
  import { useEffect, useState } from "react"
8
19
 
9
- import { useSession } from "~/auth/client"
10
- import { cn, buttonVariants } from "@vexcms/react"
20
+ import type { HeaderBlock } from "~/vex.types"
21
+
22
+ import { BrandMark } from "~/components/BrandMark"
23
+ import { Container } from "~/components/Container"
24
+ import { MediaImage } from "~/components/MediaImage"
11
25
 
12
26
  export { headerBlock } from "./config"
13
27
 
14
- export default function HeaderBlock({ block }: BlockComponentProps) {
15
- const {
16
- logoText,
17
- logoHref = "/",
18
- menuItems,
19
- actionButtons,
20
- } = block as {
21
- logoText?: string
22
- logoImage?: string
23
- logoHref?: string
24
- menuItems?: Array<{ label: string; href: string }>
25
- actionButtons?: Array<{ label: string; href: string; variant?: string }>
26
- }
27
-
28
- const { data: session } = useSession()
29
- const role = session?.user?.role
30
- const isAdmin = role === "admin" || (Array.isArray(role) && role.includes("admin"))
31
-
32
- const [menuState, setMenuState] = useState(false)
28
+ /** Past this many nav items the bar would crowd at `md`, so the Sheet holds
29
+ * on until `lg`. The bar never wraps to two lines and never shrinks its type. */
30
+ const NAV_CROWD_THRESHOLD = 7
31
+
32
+ /** Scroll distance at which the bar takes its background and rule. */
33
+ const SCROLL_THRESHOLD = 8
34
+
35
+ /**
36
+ * Site header — a rule that appears when you scroll.
37
+ *
38
+ * Transparent over the hero at rest; past 8px it takes
39
+ * `bg-background/70 backdrop-blur-md` and a bottom border and loses 8px of
40
+ * height. The scroll listener is passive and rAF-throttled, and is the only
41
+ * scroll listener on the page.
42
+ */
43
+ export default function HeaderBlockRenderer({ block }: BlockComponentProps) {
44
+ const { actionButtons, logoHref, logoImage, logoText, menuItems } = block as HeaderBlock
45
+
46
+ const pathname = usePathname()
47
+ // Same predicate the `/admin` route runs server-side, so this link can never
48
+ // offer a destination that redirects to `/unauthorized`. Reading a role off
49
+ // the session by hand was the previous approach and never matched: roles are
50
+ // stored in `access.userRolesField` ("roles"), not better-auth's scalar
51
+ // `user.role`, so the button rendered for nobody.
52
+ const canAccessAdmin = useCanAccessAdminPanel()
53
+
33
54
  const [isScrolled, setIsScrolled] = useState(false)
55
+ const [isMenuOpen, setIsMenuOpen] = useState(false)
34
56
 
35
57
  useEffect(() => {
58
+ let frame = 0
36
59
  const handleScroll = () => {
37
- setIsScrolled(window.scrollY > 50)
60
+ if (frame) {return}
61
+ frame = window.requestAnimationFrame(() => {
62
+ frame = 0
63
+ setIsScrolled(window.scrollY > SCROLL_THRESHOLD)
64
+ })
65
+ }
66
+ handleScroll()
67
+ window.addEventListener("scroll", handleScroll, { passive: true })
68
+ return () => {
69
+ window.removeEventListener("scroll", handleScroll)
70
+ window.cancelAnimationFrame(frame)
38
71
  }
39
- window.addEventListener("scroll", handleScroll)
40
- return () => window.removeEventListener("scroll", handleScroll)
41
72
  }, [])
42
73
 
74
+ const items = menuItems ?? []
75
+ const actions = actionButtons ?? []
76
+ const isCrowded = items.length >= NAV_CROWD_THRESHOLD
77
+ // The whole bar switches at one breakpoint: `md` normally, `lg` when the nav
78
+ // is crowded. Tailwind needs both class sets written out literally.
79
+ const navClass = isCrowded ? "hidden lg:flex" : "hidden md:flex"
80
+ const triggerClass = isCrowded ? "lg:hidden" : "md:hidden"
81
+
43
82
  return (
44
- <header>
45
- <nav
46
- className="fixed z-20 w-full px-2"
47
- data-state={menuState ? "active" : undefined}
48
- >
49
- <div
50
- className={cn(
51
- "mx-auto mt-2 max-w-6xl px-6 transition-all duration-300 lg:px-12",
52
- isScrolled &&
53
- "bg-background/50 max-w-4xl rounded-2xl border backdrop-blur-lg lg:px-5"
54
- )}
55
- >
56
- <div className="relative flex flex-wrap items-center justify-between gap-6 py-3 lg:gap-0 lg:py-4">
57
- <div className="flex w-full justify-between lg:w-auto">
58
- <Link
59
- aria-label="home"
60
- className="flex items-center gap-2 text-lg font-semibold"
61
- href={logoHref}
62
- >
63
- {logoText ?? "Vex CMS"}
64
- </Link>
83
+ <header
84
+ className={cn(
85
+ "sticky top-0 z-50 transition-[background-color,border-color,height] duration-[180ms] ease-[var(--ease-emphasized)]",
86
+ isScrolled
87
+ ? "h-14 border-b border-border bg-background/70 backdrop-blur-md"
88
+ : "h-14 border-b border-transparent md:h-16"
89
+ )}
90
+ >
91
+ <Container className="flex h-full items-center justify-between gap-6">
92
+ <div className="flex items-center gap-10">
93
+ <Link
94
+ aria-label={logoText ?? "Home"}
95
+ className="flex shrink-0 items-center gap-2 text-foreground"
96
+ href={logoHref ?? "/"}
97
+ >
98
+ {logoImage?.length ? (
99
+ <MediaImage
100
+ alt={logoText ?? ""}
101
+ className="h-5 w-auto"
102
+ height={20}
103
+ value={logoImage}
104
+ width={96}
105
+ />
106
+ ) : (
107
+ <>
108
+ <BrandMark className="size-4 text-primary" />
109
+ {logoText ? (
110
+ <span className="text-[15px] font-extrabold tracking-[-0.03em]">{logoText}</span>
111
+ ) : null}
112
+ </>
113
+ )}
114
+ </Link>
65
115
 
66
- <button
67
- aria-label={menuState ? "Close Menu" : "Open Menu"}
68
- className="relative z-20 -m-2.5 -mr-4 block cursor-pointer p-2.5 lg:hidden"
69
- onClick={() => setMenuState(!menuState)}
70
- >
71
- <Menu className="m-auto size-6 duration-200 in-data-[state=active]:scale-0 in-data-[state=active]:rotate-180 in-data-[state=active]:opacity-0" />
72
- <X className="absolute inset-0 m-auto size-6 scale-0 -rotate-180 opacity-0 duration-200 in-data-[state=active]:scale-100 in-data-[state=active]:rotate-0 in-data-[state=active]:opacity-100" />
73
- </button>
74
-
75
- <div className="absolute inset-0 m-auto hidden size-fit lg:block">
76
- <ul className="flex gap-8 text-sm">
77
- {(menuItems ?? []).map((item, index) => (
78
- <li key={index}>
79
- <Link
80
- className="text-muted-foreground hover:text-accent-foreground block duration-150"
81
- href={item.href}
82
- >
83
- <span>{item.label}</span>
84
- </Link>
85
- </li>
86
- ))}
87
- </ul>
88
- </div>
89
- </div>
116
+ {items.length > 0 ? (
117
+ <nav className={cn("items-center gap-1", navClass)}>
118
+ {items.map((item) => {
119
+ const isCurrent = pathname === item.href
120
+ return (
121
+ <Link
122
+ aria-current={isCurrent ? "page" : undefined}
123
+ className={cn(
124
+ "rounded-sm px-2.5 py-1.5 text-sm transition-colors duration-[180ms] ease-[var(--ease-emphasized)] active:translate-y-px",
125
+ isCurrent
126
+ ? "bg-accent text-foreground"
127
+ : "text-muted-foreground hover:bg-accent hover:text-foreground"
128
+ )}
129
+ href={item.href}
130
+ key={`${item.label}-${item.href}`}
131
+ >
132
+ {item.label}
133
+ </Link>
134
+ )
135
+ })}
136
+ </nav>
137
+ ) : null}
138
+ </div>
90
139
 
91
- <div className="hidden lg:flex lg:gap-3">
92
- {(actionButtons ?? []).map((button, index) => (
140
+ <div className="flex items-center gap-2">
141
+ {actions.length > 0 ? (
142
+ <div className={cn("items-center gap-2", navClass)}>
143
+ {actions.map((action) => (
93
144
  <Link
94
- key={index}
95
- href={button.href}
96
145
  className={cn(
97
146
  buttonVariants({
98
147
  size: "sm",
99
- variant: (button.variant as "default" | "outline" | "ghost") ?? "default",
100
- })
148
+ variant:
149
+ (action.variant?.[0]) ??
150
+ "default",
151
+ }),
152
+ "active:translate-y-px"
101
153
  )}
154
+ href={action.href}
155
+ key={`${action.label}-${action.href}`}
102
156
  >
103
- <span>{button.label}</span>
157
+ {action.label}
104
158
  </Link>
105
159
  ))}
106
- {isAdmin && (
107
- <Link
108
- href="/admin"
109
- className={cn(
110
- buttonVariants({ size: "sm", variant: "outline" }),
111
- "gap-1.5"
112
- )}
113
- >
114
- <Settings className="size-3.5" />
115
- <span>Admin</span>
116
- </Link>
117
- )}
118
160
  </div>
161
+ ) : null}
119
162
 
120
- {/* Mobile menu */}
121
- <div className="bg-background absolute top-[125%] z-10 mb-6 hidden w-full flex-wrap items-center justify-end space-y-8 rounded-3xl border p-6 shadow-2xl shadow-zinc-300/20 in-data-[state=active]:block md:flex-nowrap lg:m-0 lg:w-fit lg:space-y-0 lg:border-transparent lg:bg-transparent lg:p-0 lg:shadow-none dark:shadow-none dark:lg:bg-transparent">
122
- <div className="lg:hidden">
123
- <ul className="space-y-6 text-base">
124
- {(menuItems ?? []).map((item, index) => (
125
- <li key={index}>
126
- <Link
127
- className="text-muted-foreground hover:text-accent-foreground block duration-150"
128
- href={item.href}
129
- >
130
- <span>{item.label}</span>
131
- </Link>
132
- </li>
163
+ {/* Template affordance, not part of the marketing design: a signed-in
164
+ admin gets a direct route to the panel even when the seeded nav
165
+ does not list one. */}
166
+ {canAccessAdmin ? (
167
+ <Link
168
+ className={cn(
169
+ buttonVariants({ size: "sm", variant: "outline" }),
170
+ navClass,
171
+ "gap-1.5 active:translate-y-px"
172
+ )}
173
+ href="/admin"
174
+ >
175
+ <Settings className="size-3.5" />
176
+ Admin
177
+ </Link>
178
+ ) : null}
179
+
180
+ {items.length > 0 || actions.length > 0 ? (
181
+ <Sheet onOpenChange={setIsMenuOpen} open={isMenuOpen}>
182
+ <SheetTrigger
183
+ aria-label="Open menu"
184
+ className={cn(buttonVariants({ size: "icon-sm", variant: "ghost" }), triggerClass)}
185
+ >
186
+ <Menu className="size-5" />
187
+ </SheetTrigger>
188
+ <SheetContent className="rounded-lg border-border bg-background" side="top">
189
+ <SheetTitle className="sr-only">Menu</SheetTitle>
190
+ <nav className="flex flex-col px-2 pt-2">
191
+ {items.map((item) => (
192
+ <Link
193
+ aria-current={pathname === item.href ? "page" : undefined}
194
+ className={cn(
195
+ "flex min-h-11 items-center rounded-sm px-3 text-[15px] transition-colors duration-[180ms] active:translate-y-px",
196
+ pathname === item.href
197
+ ? "bg-accent text-foreground"
198
+ : "text-muted-foreground hover:bg-accent hover:text-foreground"
199
+ )}
200
+ href={item.href}
201
+ key={`sheet-${item.label}-${item.href}`}
202
+ onClick={() => setIsMenuOpen(false)}
203
+ >
204
+ {item.label}
205
+ </Link>
133
206
  ))}
134
- </ul>
135
-
136
- {(actionButtons && actionButtons.length > 0 || isAdmin) && (
137
- <div className="mt-6 flex flex-col space-y-3">
138
- {(actionButtons ?? []).map((button, index) => (
139
- <Link
140
- key={index}
141
- href={button.href}
142
- className={cn(
143
- buttonVariants({
144
- size: "sm",
145
- variant: (button.variant as "default" | "outline" | "ghost") ?? "default",
146
- })
147
- )}
148
- >
149
- <span>{button.label}</span>
150
- </Link>
151
- ))}
152
- {isAdmin && (
153
- <Link
154
- href="/admin"
155
- className={cn(
156
- buttonVariants({ size: "sm", variant: "outline" }),
157
- "gap-1.5"
158
- )}
159
- >
160
- <Settings className="size-3.5" />
161
- <span>Admin</span>
162
- </Link>
163
- )}
164
- </div>
165
- )}
166
- </div>
167
- </div>
168
- </div>
207
+ </nav>
208
+
209
+ {actions.length > 0 ? (
210
+ <>
211
+ <Separator className="my-4" />
212
+ <div className="flex flex-col gap-2 px-2 pb-2">
213
+ {actions.map((action) => (
214
+ <Link
215
+ className={cn(
216
+ buttonVariants({
217
+ size: "default",
218
+ variant:
219
+ (action.variant?.[0]) ?? "default",
220
+ }),
221
+ "w-full"
222
+ )}
223
+ href={action.href}
224
+ key={`sheet-${action.label}-${action.href}`}
225
+ onClick={() => setIsMenuOpen(false)}
226
+ >
227
+ {action.label}
228
+ </Link>
229
+ ))}
230
+ </div>
231
+ </>
232
+ ) : null}
233
+ </SheetContent>
234
+ </Sheet>
235
+ ) : null}
169
236
  </div>
170
- </nav>
237
+ </Container>
171
238
  </header>
172
239
  )
173
240
  }
@@ -1,4 +1,4 @@
1
- import { defineBlock, text } from "@vexcms/core"
1
+ import { defineBlock, select, text } from "@vexcms/core"
2
2
 
3
3
  import { BLOCK_SLUG_HERO } from "../constants"
4
4
 
@@ -6,35 +6,50 @@ export const heroBlock = defineBlock({
6
6
  slug: BLOCK_SLUG_HERO,
7
7
  label: "Hero Section",
8
8
  fields: {
9
+ variant: select({
10
+ label: "Variant",
11
+ description:
12
+ "Full is the landing hero — 90vh, centred, decorative background. Compact is an interior page-header band.",
13
+ options: [
14
+ { label: "Full", value: "full" },
15
+ { label: "Compact", value: "compact" },
16
+ ],
17
+ defaultValue: ["full"],
18
+ }),
9
19
  badgeText: text({
10
20
  label: "Badge Text",
11
- defaultValue: "Now in public beta",
12
- description: "Small text shown in the announcement badge",
21
+ defaultValue: "v0.1.0-alpha — now on npm",
22
+ description:
23
+ "Announcement pill on the full variant; the uppercase eyebrow on the compact one.",
13
24
  }),
14
25
  badgeLink: text({
15
26
  label: "Badge Link",
16
- defaultValue: "/docs",
17
- description: "URL the badge links to",
27
+ defaultValue: "https://www.npmjs.com/package/@vexcms/core",
28
+ description: "Optional. Without it the badge renders as plain text, not a link.",
18
29
  }),
19
30
  heading: text({
20
31
  label: "Heading",
21
32
  required: true,
22
- defaultValue: "The CMS built for Convex",
33
+ defaultValue: "The CMS that thinks in types.",
23
34
  }),
24
35
  subheading: text({
25
36
  label: "Subheading",
26
37
  defaultValue:
27
- "Vex CMS gives you a full-featured content management system powered by Convex — real-time data, type-safe schemas, and a beautiful admin panel out of the box.",
38
+ "A headless CMS built natively on Convex. Declare your collections in TypeScript and Vex generates the Convex schema, the types, and the queries — no translation layer. Every edit reaches every subscriber in milliseconds.",
39
+ }),
40
+ installCommand: text({
41
+ label: "Install Command",
42
+ defaultValue: "pnpm create vexcms@alpha",
43
+ description:
44
+ "Optional. Renders a copy-to-clipboard command row under the CTAs. Full variant only.",
28
45
  }),
29
46
  primaryCtaLabel: text({
30
47
  label: "Primary CTA Label",
31
- required: true,
32
- defaultValue: "Get Started",
48
+ defaultValue: "Read the docs",
33
49
  }),
34
50
  primaryCtaHref: text({
35
51
  label: "Primary CTA Link",
36
- required: true,
37
- defaultValue: "/docs",
52
+ defaultValue: "https://docs.vexcms.dev/guides/quickstart/",
38
53
  }),
39
54
  secondaryCtaLabel: text({
40
55
  label: "Secondary CTA Label",
@@ -42,7 +57,7 @@ export const heroBlock = defineBlock({
42
57
  }),
43
58
  secondaryCtaHref: text({
44
59
  label: "Secondary CTA Link",
45
- defaultValue: "https://github.com/vexcms/vex",
60
+ defaultValue: "https://github.com/ianyimi/vex",
46
61
  }),
47
62
  },
48
63
  admin: {