create-agntcms-app 0.2.1

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 (197) hide show
  1. package/README.md +39 -0
  2. package/dist/index.mjs +297 -0
  3. package/dist/template/.claude/settings.json +6 -0
  4. package/dist/template/.claude/skills/.gitkeep +0 -0
  5. package/dist/template/.claude-plugin/channel/server.mjs +254 -0
  6. package/dist/template/.claude-plugin/channel/server.ts +369 -0
  7. package/dist/template/.claude-plugin/plugin.json +17 -0
  8. package/dist/template/.mcp.json +8 -0
  9. package/dist/template/BRAND.md +49 -0
  10. package/dist/template/CLAUDE.md +157 -0
  11. package/dist/template/agntcms/config.ts +49 -0
  12. package/dist/template/agntcms/sections/ArticleBody/component.tsx +32 -0
  13. package/dist/template/agntcms/sections/ArticleBody/index.ts +10 -0
  14. package/dist/template/agntcms/sections/ArticleBody/schema.ts +5 -0
  15. package/dist/template/agntcms/sections/ArticleHero/component.tsx +87 -0
  16. package/dist/template/agntcms/sections/ArticleHero/index.ts +10 -0
  17. package/dist/template/agntcms/sections/ArticleHero/schema.ts +12 -0
  18. package/dist/template/agntcms/sections/Banner/component.tsx +83 -0
  19. package/dist/template/agntcms/sections/Banner/index.ts +10 -0
  20. package/dist/template/agntcms/sections/Banner/schema.ts +9 -0
  21. package/dist/template/agntcms/sections/BlogIndex/component.tsx +173 -0
  22. package/dist/template/agntcms/sections/BlogIndex/index.ts +10 -0
  23. package/dist/template/agntcms/sections/BlogIndex/schema.ts +33 -0
  24. package/dist/template/agntcms/sections/BlogIndexHeader/component.tsx +44 -0
  25. package/dist/template/agntcms/sections/BlogIndexHeader/index.ts +10 -0
  26. package/dist/template/agntcms/sections/BlogIndexHeader/schema.ts +8 -0
  27. package/dist/template/agntcms/sections/BlogPostBody/component.tsx +50 -0
  28. package/dist/template/agntcms/sections/BlogPostBody/index.ts +10 -0
  29. package/dist/template/agntcms/sections/BlogPostBody/schema.ts +10 -0
  30. package/dist/template/agntcms/sections/BlogPostHero/component.tsx +88 -0
  31. package/dist/template/agntcms/sections/BlogPostHero/index.ts +10 -0
  32. package/dist/template/agntcms/sections/BlogPostHero/schema.ts +35 -0
  33. package/dist/template/agntcms/sections/CaseStudies/component.tsx +92 -0
  34. package/dist/template/agntcms/sections/CaseStudies/index.ts +10 -0
  35. package/dist/template/agntcms/sections/CaseStudies/schema.ts +17 -0
  36. package/dist/template/agntcms/sections/ContactForm/component.tsx +119 -0
  37. package/dist/template/agntcms/sections/ContactForm/index.ts +10 -0
  38. package/dist/template/agntcms/sections/ContactForm/schema.ts +15 -0
  39. package/dist/template/agntcms/sections/DocsArticle/component.tsx +266 -0
  40. package/dist/template/agntcms/sections/DocsArticle/index.ts +10 -0
  41. package/dist/template/agntcms/sections/DocsArticle/schema.ts +33 -0
  42. package/dist/template/agntcms/sections/FAQ/component.tsx +57 -0
  43. package/dist/template/agntcms/sections/FAQ/index.ts +10 -0
  44. package/dist/template/agntcms/sections/FAQ/schema.ts +11 -0
  45. package/dist/template/agntcms/sections/FeatureGrid/component.tsx +117 -0
  46. package/dist/template/agntcms/sections/FeatureGrid/index.ts +10 -0
  47. package/dist/template/agntcms/sections/FeatureGrid/schema.ts +21 -0
  48. package/dist/template/agntcms/sections/FeaturedArticles/component.tsx +99 -0
  49. package/dist/template/agntcms/sections/FeaturedArticles/index.ts +10 -0
  50. package/dist/template/agntcms/sections/FeaturedArticles/schema.ts +17 -0
  51. package/dist/template/agntcms/sections/GettingStarted/component.tsx +116 -0
  52. package/dist/template/agntcms/sections/GettingStarted/index.ts +10 -0
  53. package/dist/template/agntcms/sections/GettingStarted/schema.ts +11 -0
  54. package/dist/template/agntcms/sections/Hero/component.tsx +148 -0
  55. package/dist/template/agntcms/sections/Hero/index.ts +10 -0
  56. package/dist/template/agntcms/sections/Hero/schema.ts +16 -0
  57. package/dist/template/agntcms/sections/HowItWorks/component.tsx +57 -0
  58. package/dist/template/agntcms/sections/HowItWorks/index.ts +10 -0
  59. package/dist/template/agntcms/sections/HowItWorks/schema.ts +11 -0
  60. package/dist/template/agntcms/sections/ImageText/component.tsx +110 -0
  61. package/dist/template/agntcms/sections/ImageText/index.ts +10 -0
  62. package/dist/template/agntcms/sections/ImageText/schema.ts +14 -0
  63. package/dist/template/agntcms/sections/LogoStrip/component.tsx +37 -0
  64. package/dist/template/agntcms/sections/LogoStrip/index.ts +10 -0
  65. package/dist/template/agntcms/sections/LogoStrip/schema.ts +6 -0
  66. package/dist/template/agntcms/sections/Newsletter/component.tsx +48 -0
  67. package/dist/template/agntcms/sections/Newsletter/index.ts +10 -0
  68. package/dist/template/agntcms/sections/Newsletter/schema.ts +8 -0
  69. package/dist/template/agntcms/sections/OpenSource/component.tsx +99 -0
  70. package/dist/template/agntcms/sections/OpenSource/index.ts +10 -0
  71. package/dist/template/agntcms/sections/OpenSource/schema.ts +13 -0
  72. package/dist/template/agntcms/sections/PainAnswer/component.tsx +81 -0
  73. package/dist/template/agntcms/sections/PainAnswer/index.ts +10 -0
  74. package/dist/template/agntcms/sections/PainAnswer/schema.ts +15 -0
  75. package/dist/template/agntcms/sections/PricingPlans/component.tsx +100 -0
  76. package/dist/template/agntcms/sections/PricingPlans/index.ts +10 -0
  77. package/dist/template/agntcms/sections/PricingPlans/schema.ts +13 -0
  78. package/dist/template/agntcms/sections/Problem/component.tsx +49 -0
  79. package/dist/template/agntcms/sections/Problem/index.ts +10 -0
  80. package/dist/template/agntcms/sections/Problem/schema.ts +12 -0
  81. package/dist/template/agntcms/sections/SiteFooter/component.tsx +88 -0
  82. package/dist/template/agntcms/sections/SiteFooter/index.ts +10 -0
  83. package/dist/template/agntcms/sections/SiteFooter/schema.ts +13 -0
  84. package/dist/template/agntcms/sections/SiteHeader/component.tsx +99 -0
  85. package/dist/template/agntcms/sections/SiteHeader/index.ts +10 -0
  86. package/dist/template/agntcms/sections/SiteHeader/schema.ts +14 -0
  87. package/dist/template/agntcms/sections/SiteMeta/component.tsx +26 -0
  88. package/dist/template/agntcms/sections/SiteMeta/index.ts +13 -0
  89. package/dist/template/agntcms/sections/SiteMeta/schema.ts +18 -0
  90. package/dist/template/agntcms/sections/TabbedFeatures/component.tsx +120 -0
  91. package/dist/template/agntcms/sections/TabbedFeatures/index.ts +10 -0
  92. package/dist/template/agntcms/sections/TabbedFeatures/schema.ts +13 -0
  93. package/dist/template/agntcms/sections/TeamGrid/component.tsx +77 -0
  94. package/dist/template/agntcms/sections/TeamGrid/index.ts +10 -0
  95. package/dist/template/agntcms/sections/TeamGrid/schema.ts +14 -0
  96. package/dist/template/agntcms/sections/Testimonials/component.tsx +76 -0
  97. package/dist/template/agntcms/sections/Testimonials/index.ts +10 -0
  98. package/dist/template/agntcms/sections/Testimonials/schema.ts +12 -0
  99. package/dist/template/agntcms/sections/WhatIsBuilt/component.tsx +86 -0
  100. package/dist/template/agntcms/sections/WhatIsBuilt/index.ts +10 -0
  101. package/dist/template/agntcms/sections/WhatIsBuilt/schema.ts +20 -0
  102. package/dist/template/agntcms/site-meta.ts +81 -0
  103. package/dist/template/app/[[...slug]]/page.tsx +123 -0
  104. package/dist/template/app/admin/AdminPageClient.tsx +77 -0
  105. package/dist/template/app/admin/AdminPageDynamic.tsx +24 -0
  106. package/dist/template/app/admin/page.tsx +14 -0
  107. package/dist/template/app/api/agntcms/_shared.ts +80 -0
  108. package/dist/template/app/api/agntcms/assets/route.ts +11 -0
  109. package/dist/template/app/api/agntcms/assets/upload/route.ts +11 -0
  110. package/dist/template/app/api/agntcms/draft/discard/route.ts +12 -0
  111. package/dist/template/app/api/agntcms/draft/list/route.ts +11 -0
  112. package/dist/template/app/api/agntcms/draft/publish/route.ts +11 -0
  113. package/dist/template/app/api/agntcms/draft/reorder/route.ts +10 -0
  114. package/dist/template/app/api/agntcms/draft/save/route.ts +11 -0
  115. package/dist/template/app/api/agntcms/events/route.ts +12 -0
  116. package/dist/template/app/api/agntcms/forms/delete/route.ts +17 -0
  117. package/dist/template/app/api/agntcms/forms/list/route.ts +24 -0
  118. package/dist/template/app/api/agntcms/forms/read/route.ts +23 -0
  119. package/dist/template/app/api/agntcms/forms/submit/route.ts +17 -0
  120. package/dist/template/app/api/agntcms/global/delete/route.ts +13 -0
  121. package/dist/template/app/api/agntcms/global/history/route.ts +10 -0
  122. package/dist/template/app/api/agntcms/global/list/route.ts +14 -0
  123. package/dist/template/app/api/agntcms/global/read/route.ts +11 -0
  124. package/dist/template/app/api/agntcms/global/rollback/route.ts +10 -0
  125. package/dist/template/app/api/agntcms/global/save/route.ts +14 -0
  126. package/dist/template/app/api/agntcms/mcp/route.ts +12 -0
  127. package/dist/template/app/api/agntcms/page/delete/route.ts +10 -0
  128. package/dist/template/app/api/agntcms/page/duplicate/route.ts +11 -0
  129. package/dist/template/app/api/agntcms/page/history/route.ts +10 -0
  130. package/dist/template/app/api/agntcms/page/list/route.ts +10 -0
  131. package/dist/template/app/api/agntcms/page/read/route.ts +11 -0
  132. package/dist/template/app/api/agntcms/page/rename/route.ts +10 -0
  133. package/dist/template/app/api/agntcms/page/rollback/route.ts +10 -0
  134. package/dist/template/app/api/agntcms/page/unpublish/route.ts +11 -0
  135. package/dist/template/app/api/agntcms/preview/enter/route.ts +13 -0
  136. package/dist/template/app/api/agntcms/preview/exit/route.ts +10 -0
  137. package/dist/template/app/api/agntcms/preview/issue/route.ts +12 -0
  138. package/dist/template/app/api/agntcms/template/list/route.ts +15 -0
  139. package/dist/template/app/apple-icon.svg +9 -0
  140. package/dist/template/app/icon.svg +9 -0
  141. package/dist/template/app/layout.tsx +107 -0
  142. package/dist/template/app/not-found.tsx +75 -0
  143. package/dist/template/app/robots.ts +33 -0
  144. package/dist/template/app/sitemap.ts +49 -0
  145. package/dist/template/content/globals/site-footer.json +53 -0
  146. package/dist/template/content/globals/site-header.json +18 -0
  147. package/dist/template/content/globals/site-meta.json +13 -0
  148. package/dist/template/content/pages/404.json +34 -0
  149. package/dist/template/content/pages/about.json +307 -0
  150. package/dist/template/content/pages/article-editor.json +61 -0
  151. package/dist/template/content/pages/article-schemas.json +61 -0
  152. package/dist/template/content/pages/blog.json +162 -0
  153. package/dist/template/content/pages/contact.json +29 -0
  154. package/dist/template/content/pages/home.json +243 -0
  155. package/dist/template/content/pages/pricing.json +219 -0
  156. package/dist/template/content/pages/services.json +177 -0
  157. package/dist/template/fonts/Satoshi-Medium.woff2 +0 -0
  158. package/dist/template/fonts/Satoshi-Regular.woff2 +0 -0
  159. package/dist/template/next.config.ts +6 -0
  160. package/dist/template/package.json +36 -0
  161. package/dist/template/postcss.config.mjs +5 -0
  162. package/dist/template/public/assets/.gitkeep +0 -0
  163. package/dist/template/public/assets/0418d7ed21f57e7b9e0546725c92b8419daeaa355675d9070fab0c2013cf1524.jpg +0 -0
  164. package/dist/template/public/assets/0d0475f21aa96435a8ed3cdb2fddcc6278492e76ae842f569432454f4d33631a.jpg +0 -0
  165. package/dist/template/public/assets/27457a1adee2372030d9876b0d52c44d46be98843999935eaef2526b9b961f12.jpg +0 -0
  166. package/dist/template/public/assets/3855d91192f0c6120b01427b78ef84e52baa9f4b5a17d4271e41c1bfd95a5b0c.jpg +0 -0
  167. package/dist/template/public/assets/3b3b90c5084635b746be673ede92a328f002f5621a42c9a5cb89c5e2435652cb.jpg +0 -0
  168. package/dist/template/public/assets/3e76165a78fd3e7b8ed1e93dee50803ae11110c756c8c1c89229a2dec2bc0abf.jpg +0 -0
  169. package/dist/template/public/assets/4a3e28f85dc850c347ea0fd931696aa936a6bd45f193e7f1c9328b5896fb272c.jpg +0 -0
  170. package/dist/template/public/assets/579f67d5fd4c9106c6cdf2ef29f50df934ad0fc2b7849bac1e1cfb1e3f92303b.jpg +0 -0
  171. package/dist/template/public/assets/5b95209269661bb60fb250f1da682e05b9efa64dd42f350608b299e6bf1f2f35.jpg +0 -0
  172. package/dist/template/public/assets/5e04b46f8317ef95a7ddf85aedfe5c098a755f05056325d0251eccf95ce51172.jpg +0 -0
  173. package/dist/template/public/assets/6167a9164be2cf1183bdfdd4946bf9b908570e79e92a2380c25f0bb702422bbd.jpg +0 -0
  174. package/dist/template/public/assets/75e723ec316de28247924e5dfb73a4b266e10de605e749f150883d280ed8ed16.jpg +0 -0
  175. package/dist/template/public/assets/816a11e6a7245feaf51bbebf09d1bda3f125b334bc24fc3b8f47b5380a7b4294.jpg +0 -0
  176. package/dist/template/public/assets/81eba6f5654b8746a9b0cba1a9521a67f2b4afaaefc7c88d66dfab1461270d8f.jpg +0 -0
  177. package/dist/template/public/assets/82a2ce9e49361098f77a28755779dc5a7c026831cbd135175749c1304e21dacc.jpg +0 -0
  178. package/dist/template/public/assets/8d7b02ba277ba56bdafdbd47b01f7df6d993c714b4dc2305eb65a1307c09647d.jpg +0 -0
  179. package/dist/template/public/assets/b303185b471678e4d62f678a1549ee26022f4745407d08cae44ecb1c25352293.jpg +0 -0
  180. package/dist/template/public/assets/b69b49169c11546100d6dd5280073bc0d84cbbcc6d33fa01ecf6a5866fa42237.jpg +0 -0
  181. package/dist/template/public/assets/c4d2f0d1a310e457ac722a399693652e3c86c55b294243d5ffc679394e12f9d1.jpg +0 -0
  182. package/dist/template/public/assets/cae09f4729f8a348b67267c2f2a550be0f3bfa420689afe1a5cf8b7e2b146238.png +0 -0
  183. package/dist/template/public/assets/cb3acf58b57417a4b26474ba04c096af7103c4320ed2f4f3683f79d7670a055c.jpg +0 -0
  184. package/dist/template/public/assets/d5a0701b2d156284e0ce851cd2534ec632db34f91fbcbee3b8a7784d45ce78d2.jpg +0 -0
  185. package/dist/template/public/assets/d6ef1c3f48b0e488521794fb60701da1fd2c3a1621d6ac5f17ccfd4909d3be60.jpg +0 -0
  186. package/dist/template/public/assets/de249ff9be2539cf0d1ce092de3c57001839b6c3e14fcee3fc31a7b7673ae007.jpg +0 -0
  187. package/dist/template/public/assets/eac45438956be187b010e24b3289757aa00f227c190d49ee99fea510552dd2ba.jpg +0 -0
  188. package/dist/template/public/assets/f8b9200065b5436c6a88361839edc2b89be88d3037c84a80d3ee95c32891510b.jpg +0 -0
  189. package/dist/template/public/assets/placeholder.png +0 -0
  190. package/dist/template/public/brand/mark.svg +6 -0
  191. package/dist/template/public/brand/wordmark-light.svg +6 -0
  192. package/dist/template/public/brand/wordmark.svg +6 -0
  193. package/dist/template/styles/globals.css +69 -0
  194. package/dist/template/styles/theme.css +492 -0
  195. package/dist/template/styles/typography.css +469 -0
  196. package/dist/template/tsconfig.json +30 -0
  197. package/package.json +30 -0
@@ -0,0 +1,88 @@
1
+ 'use client'
2
+
3
+ import {
4
+ EditableImage,
5
+ EditableList,
6
+ EditableRichText,
7
+ } from '@agntcms/next/client'
8
+ import type { EditableSlot, SlotItem } from '@agntcms/next/client'
9
+ import type { ImageValue } from '@agntcms/next'
10
+ import { schema } from './schema'
11
+
12
+ type Tag = SlotItem<typeof schema.tags.itemSchema>
13
+
14
+ interface Props {
15
+ readonly category: EditableSlot<'richText', string>
16
+ readonly title: EditableSlot<'richText', string>
17
+ readonly summary: EditableSlot<'richText', string>
18
+ readonly author: EditableSlot<'richText', string>
19
+ readonly publishedAt: EditableSlot<'richText', string>
20
+ readonly readingTime: EditableSlot<'richText', string>
21
+ readonly cover: EditableSlot<'image', ImageValue>
22
+ readonly tags: EditableSlot<'list', ReadonlyArray<Tag>>
23
+ }
24
+
25
+ export function BlogPostHeroComponent({
26
+ category,
27
+ title,
28
+ summary,
29
+ author,
30
+ publishedAt,
31
+ readingTime,
32
+ cover,
33
+ tags,
34
+ }: Props) {
35
+ return (
36
+ <section className="bg-bg-primary">
37
+ <div className="mx-auto w-full max-w-[920px] px-8 pt-24 pb-12">
38
+ <EditableRichText
39
+ field={category}
40
+ as="div"
41
+ className="mb-6 text-[11px] font-medium uppercase tracking-[0.10em] text-text-brand-primary"
42
+ />
43
+ <EditableRichText
44
+ field={title}
45
+ className="
46
+ mb-6
47
+ [&_h1]:m-0 [&_h1]:font-display [&_h1]:font-medium [&_h1]:text-text-primary
48
+ [&_h1]:max-w-[22ch]
49
+ [&_h1]:!text-[clamp(34px,5vw,56px)] [&_h1]:!leading-[1.05] [&_h1]:!tracking-[-0.03em]
50
+ "
51
+ />
52
+ <EditableRichText
53
+ field={summary}
54
+ className="
55
+ mb-10 max-w-[60ch]
56
+ [&_p]:m-0 [&_p]:text-[18px] [&_p]:leading-[1.6] [&_p]:text-text-secondary
57
+ "
58
+ />
59
+
60
+ <div className="flex flex-wrap items-center gap-x-3 gap-y-2 text-[13px] text-text-secondary">
61
+ <EditableRichText field={author} as="span" className="font-medium text-text-primary" />
62
+ <span className="text-text-tertiary" aria-hidden="true">·</span>
63
+ <EditableRichText field={publishedAt} as="span" />
64
+ <span className="text-text-tertiary" aria-hidden="true">·</span>
65
+ <EditableRichText field={readingTime} as="span" />
66
+ </div>
67
+
68
+ <EditableList
69
+ field={tags}
70
+ itemSchema={schema.tags.itemSchema}
71
+ className="mt-5 flex flex-wrap gap-2"
72
+ renderItem={(item) => (
73
+ <span className="inline-flex items-center rounded-sm border-[0.5px] border-border-primary bg-bg-secondary px-2.5 py-1 text-[11px] font-medium uppercase tracking-[0.10em] text-text-secondary">
74
+ <EditableRichText field={item.label} as="span" />
75
+ </span>
76
+ )}
77
+ />
78
+
79
+ <div className="mt-12 overflow-hidden rounded-xl border-[0.5px] border-border-primary bg-bg-secondary">
80
+ <EditableImage
81
+ field={cover}
82
+ className="block aspect-[16/9] w-full object-cover"
83
+ />
84
+ </div>
85
+ </div>
86
+ </section>
87
+ )
88
+ }
@@ -0,0 +1,10 @@
1
+ import { defineSection } from '@agntcms/next'
2
+ import { schema } from './schema'
3
+ import { BlogPostHeroComponent } from './component'
4
+
5
+ export const BlogPostHero = defineSection({
6
+ name: 'BlogPostHero',
7
+ category: 'Blog',
8
+ schema,
9
+ component: BlogPostHeroComponent,
10
+ })
@@ -0,0 +1,35 @@
1
+ import { ImageField, ListField, RichTextField } from '@agntcms/next'
2
+
3
+ export const schema = {
4
+ // ALL CAPS section label — category for the post (e.g. "Product", "Engineering").
5
+ category: { kind: 'richText' as const, default: 'Product' },
6
+
7
+ // Display title rendered as Satoshi 500 H1.
8
+ title: {
9
+ kind: 'richText' as const,
10
+ default: '# A new post.',
11
+ },
12
+
13
+ // 1–2 sentence summary shown under the headline.
14
+ summary: {
15
+ kind: 'richText' as const,
16
+ default: 'A short standfirst that frames the post in plain language.',
17
+ },
18
+
19
+ // Author name rendered as plain text byline. Single author by default.
20
+ author: { kind: 'richText' as const, default: 'agntcms Team' },
21
+
22
+ // Publication date — free-form string, e.g. "May 1, 2026".
23
+ publishedAt: { kind: 'richText' as const, default: 'May 1, 2026' },
24
+
25
+ // Approximate reading time, e.g. "5 min read".
26
+ readingTime: { kind: 'richText' as const, default: '5 min read' },
27
+
28
+ // Hero cover image. Required; alt collected via the image picker.
29
+ cover: ImageField,
30
+
31
+ // Tags rendered as a row of small uppercase chips.
32
+ tags: ListField({
33
+ label: RichTextField,
34
+ }),
35
+ }
@@ -0,0 +1,92 @@
1
+ 'use client'
2
+
3
+ import {
4
+ EditableRichText,
5
+ EditableText,
6
+ EditableImage,
7
+ EditableList,
8
+ } from '@agntcms/next/client'
9
+ import type { EditableSlot, SlotItem } from '@agntcms/next/client'
10
+ import { schema } from './schema'
11
+
12
+ type Item = SlotItem<typeof schema.items.itemSchema>
13
+
14
+ interface Props {
15
+ readonly eyebrow: EditableSlot<'richText', string>
16
+ readonly headline: EditableSlot<'richText', string>
17
+ readonly lead: EditableSlot<'richText', string>
18
+ readonly items: EditableSlot<'list', ReadonlyArray<Item>>
19
+ }
20
+
21
+ export function CaseStudiesComponent({ eyebrow, headline, lead, items }: Props) {
22
+ return (
23
+ <section className="bg-paper-2">
24
+ <div className="mx-auto w-full max-w-[1280px] px-8 py-[88px]">
25
+ <div className="max-w-[720px]">
26
+ <EditableRichText
27
+ field={eyebrow}
28
+ className="[&_p]:font-mono [&_p]:font-medium [&_p]:text-[12px] [&_p]:tracking-[0.07em] [&_p]:uppercase [&_p]:text-ink-3 [&_p]:m-0"
29
+ />
30
+ <div className="mt-3 [&_h2]:m-0 [&_h2]:font-display [&_h2]:font-semibold [&_h2]:text-ink [&_h2]:text-[44px] [&_h2]:leading-[1.05] [&_h2]:tracking-[-0.03em] [&_p]:m-0 [&_p]:font-display [&_p]:font-semibold [&_p]:text-ink [&_p]:text-[44px] [&_p]:leading-[1.05] [&_p]:tracking-[-0.03em] [&_em]:not-italic [&_em]:text-ink-3 [&_em]:font-semibold">
31
+ <EditableRichText field={headline} />
32
+ </div>
33
+ <EditableRichText
34
+ field={lead}
35
+ className="mt-4 max-w-[580px] [&_p]:text-[18px] [&_p]:leading-[1.55] [&_p]:text-ink-2 [&_p]:m-0"
36
+ />
37
+ </div>
38
+ <EditableList
39
+ field={items}
40
+ itemSchema={schema.items.itemSchema}
41
+ className="mt-12 grid grid-cols-1 border-t border-hairline lg:grid-cols-3"
42
+ renderItem={(p) => (
43
+ <div className="block border-b border-r border-hairline -mr-px last:mr-0">
44
+ <div className="aspect-[4/3] overflow-hidden border-b border-hairline bg-paper-3">
45
+ <EditableImage
46
+ field={p.image}
47
+ className="!block h-full w-full object-cover [&_img]:h-full [&_img]:w-full [&_img]:object-cover"
48
+ />
49
+ </div>
50
+ <div className="p-6">
51
+ <EditableText
52
+ field={p.kind}
53
+ as="span"
54
+ className="font-mono text-[11px] font-medium tracking-[0.07em] uppercase text-ink-3"
55
+ />
56
+ <EditableText
57
+ field={p.name}
58
+ as="h4"
59
+ className="mt-2.5 mb-1.5 text-[19px] font-semibold tracking-[-0.015em] text-ink"
60
+ />
61
+ <EditableRichText
62
+ field={p.body}
63
+ className="[&_p]:m-0 [&_p]:text-[14px] [&_p]:leading-[1.55] [&_p]:text-ink-3"
64
+ />
65
+ <div className="mt-4 flex gap-6 border-t border-hairline pt-4 font-mono text-[11px] tracking-[0.07em] uppercase text-ink-3">
66
+ <span>
67
+ <EditableText
68
+ field={p.metric1Value}
69
+ as="b"
70
+ className="font-semibold text-[13px] text-ink"
71
+ />{' '}
72
+ ·{' '}
73
+ <EditableText field={p.metric1Label} as="span" />
74
+ </span>
75
+ <span>
76
+ <EditableText
77
+ field={p.metric2Value}
78
+ as="b"
79
+ className="font-semibold text-[13px] text-ink"
80
+ />{' '}
81
+ ·{' '}
82
+ <EditableText field={p.metric2Label} as="span" />
83
+ </span>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ )}
88
+ />
89
+ </div>
90
+ </section>
91
+ )
92
+ }
@@ -0,0 +1,10 @@
1
+ import { defineSection } from '@agntcms/next'
2
+ import { schema } from './schema'
3
+ import { CaseStudiesComponent } from './component'
4
+
5
+ export const CaseStudies = defineSection({
6
+ name: 'CaseStudies',
7
+ category: 'Social proof',
8
+ schema,
9
+ component: CaseStudiesComponent,
10
+ })
@@ -0,0 +1,17 @@
1
+ import { RichTextField, TextField, ImageField, ListField } from '@agntcms/next'
2
+
3
+ export const schema = {
4
+ eyebrow: RichTextField,
5
+ headline: RichTextField,
6
+ lead: RichTextField,
7
+ items: ListField({
8
+ image: ImageField,
9
+ kind: TextField,
10
+ name: TextField,
11
+ body: RichTextField,
12
+ metric1Value: TextField,
13
+ metric1Label: TextField,
14
+ metric2Value: TextField,
15
+ metric2Label: TextField,
16
+ }),
17
+ }
@@ -0,0 +1,119 @@
1
+ 'use client'
2
+
3
+ import { EditableRichText, EditableText, EditableImage } from '@agntcms/next/client'
4
+ import type { EditableSlot } from '@agntcms/next/client'
5
+ import type { ImageValue } from '@agntcms/next'
6
+
7
+ interface Props {
8
+ readonly headline: EditableSlot<'richText', string>
9
+ readonly body: EditableSlot<'richText', string>
10
+ readonly nameLabel: EditableSlot<'text', string>
11
+ readonly emailLabel: EditableSlot<'text', string>
12
+ readonly companyLabel: EditableSlot<'text', string>
13
+ readonly messageLabel: EditableSlot<'text', string>
14
+ readonly submitLabel: EditableSlot<'text', string>
15
+ readonly quote: EditableSlot<'richText', string>
16
+ readonly quoteName: EditableSlot<'text', string>
17
+ readonly quoteRole: EditableSlot<'text', string>
18
+ readonly quotePhoto: EditableSlot<'image', ImageValue>
19
+ }
20
+
21
+ function Field({ label }: { label: EditableSlot<'text', string> }) {
22
+ return (
23
+ <div className="flex flex-col gap-1.5">
24
+ <EditableText
25
+ field={label}
26
+ as="span"
27
+ className="font-mono text-[11px] font-medium tracking-[0.07em] uppercase text-ink-3"
28
+ />
29
+ <input
30
+ type="text"
31
+ className="w-full border-0 border-b border-hairline bg-transparent py-2.5 text-[15px] text-ink outline-none focus:border-b-2 focus:border-ink focus:pb-[9px]"
32
+ />
33
+ </div>
34
+ )
35
+ }
36
+
37
+ export function ContactFormComponent({
38
+ headline,
39
+ body,
40
+ nameLabel,
41
+ emailLabel,
42
+ companyLabel,
43
+ messageLabel,
44
+ submitLabel,
45
+ quote,
46
+ quoteName,
47
+ quoteRole,
48
+ quotePhoto,
49
+ }: Props) {
50
+ return (
51
+ <section className="bg-paper">
52
+ <div className="mx-auto w-full max-w-[1280px] px-8 py-[88px]">
53
+ <div className="grid grid-cols-1 gap-16 lg:grid-cols-[1.2fr_1fr]">
54
+ <div>
55
+ <div className="[&_h2]:m-0 [&_h2]:font-display [&_h2]:font-semibold [&_h2]:text-ink [&_h2]:text-[48px] [&_h2]:leading-[1.05] [&_h2]:tracking-[-0.03em] [&_p]:m-0 [&_p]:font-display [&_p]:font-semibold [&_p]:text-ink [&_p]:text-[48px] [&_p]:leading-[1.05] [&_p]:tracking-[-0.03em] [&_em]:not-italic [&_em]:text-ink-3 [&_em]:font-semibold">
56
+ <EditableRichText field={headline} />
57
+ </div>
58
+ <EditableRichText
59
+ field={body}
60
+ className="mt-4 max-w-[540px] [&_p]:m-0 [&_p]:text-[17px] [&_p]:leading-[1.55] [&_p]:text-ink-2"
61
+ />
62
+ <form
63
+ onSubmit={(e) => e.preventDefault()}
64
+ className="mt-10 flex max-w-[540px] flex-col gap-5"
65
+ >
66
+ <Field label={nameLabel} />
67
+ <Field label={emailLabel} />
68
+ <Field label={companyLabel} />
69
+ <div className="flex flex-col gap-1.5">
70
+ <EditableText
71
+ field={messageLabel}
72
+ as="span"
73
+ className="font-mono text-[11px] font-medium tracking-[0.07em] uppercase text-ink-3"
74
+ />
75
+ <textarea
76
+ rows={4}
77
+ className="w-full resize-y border-0 border-b border-hairline bg-transparent py-2.5 text-[15px] text-ink outline-none focus:border-b-2 focus:border-ink focus:pb-[9px]"
78
+ />
79
+ </div>
80
+ <button
81
+ type="submit"
82
+ className="mt-2 inline-flex w-fit items-center gap-2 whitespace-nowrap rounded-sm border border-ink bg-ink px-[22px] py-[13px] text-[15px] font-medium text-paper transition-colors duration-200 ease-out hover:bg-ink-2 hover:border-ink-2"
83
+ >
84
+ <EditableText field={submitLabel} as="span" />
85
+ </button>
86
+ </form>
87
+ </div>
88
+ <aside className="self-start border border-hairline bg-transparent p-8">
89
+ <span className="font-mono text-[11px] tracking-[0.07em] uppercase text-ink-3">
90
+ customer quote
91
+ </span>
92
+ <EditableRichText
93
+ field={quote}
94
+ className="mt-5 mb-6 [&_p]:m-0 [&_p]:text-[18px] [&_p]:leading-[1.5] [&_p]:tracking-[-0.01em] [&_p]:text-ink"
95
+ />
96
+ <div className="flex items-center gap-3 border-t border-hairline pt-5">
97
+ <EditableImage
98
+ field={quotePhoto}
99
+ className="!block h-9 w-9 rounded-full object-cover grayscale [&_img]:h-9 [&_img]:w-9 [&_img]:rounded-full [&_img]:object-cover [&_img]:grayscale"
100
+ />
101
+ <div>
102
+ <EditableText
103
+ field={quoteName}
104
+ as="div"
105
+ className="text-sm font-semibold text-ink"
106
+ />
107
+ <EditableText
108
+ field={quoteRole}
109
+ as="div"
110
+ className="font-mono text-[11px] tracking-[0.07em] uppercase text-ink-3"
111
+ />
112
+ </div>
113
+ </div>
114
+ </aside>
115
+ </div>
116
+ </div>
117
+ </section>
118
+ )
119
+ }
@@ -0,0 +1,10 @@
1
+ import { defineSection } from '@agntcms/next'
2
+ import { schema } from './schema'
3
+ import { ContactFormComponent } from './component'
4
+
5
+ export const ContactForm = defineSection({
6
+ name: 'ContactForm',
7
+ category: 'Forms',
8
+ schema,
9
+ component: ContactFormComponent,
10
+ })
@@ -0,0 +1,15 @@
1
+ import { RichTextField, TextField, ImageField } from '@agntcms/next'
2
+
3
+ export const schema = {
4
+ headline: RichTextField,
5
+ body: RichTextField,
6
+ nameLabel: TextField,
7
+ emailLabel: TextField,
8
+ companyLabel: TextField,
9
+ messageLabel: TextField,
10
+ submitLabel: TextField,
11
+ quote: RichTextField,
12
+ quoteName: TextField,
13
+ quoteRole: TextField,
14
+ quotePhoto: ImageField,
15
+ }
@@ -0,0 +1,266 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef, useState } from 'react'
4
+ import {
5
+ EditableRichText,
6
+ EditableList,
7
+ EditableLink,
8
+ read,
9
+ isSlotInPreview,
10
+ } from '@agntcms/next/client'
11
+ import type { EditableSlot, SlotItem } from '@agntcms/next/client'
12
+ import { hrefOf, isExternalLink } from '@agntcms/next'
13
+ import type { LinkValue } from '@agntcms/next'
14
+ import { schema } from './schema'
15
+
16
+ type SidebarGroup = SlotItem<typeof schema.sidebar.itemSchema>
17
+ type SidebarItem = SlotItem<typeof schema.sidebar.itemSchema.items.itemSchema>
18
+
19
+ function slugify(s: string): string {
20
+ return s
21
+ .toLowerCase()
22
+ .trim()
23
+ .replace(/[^a-z0-9\s-]+/g, '')
24
+ .replace(/\s+/g, '-')
25
+ .replace(/-+/g, '-')
26
+ .replace(/^-+|-+$/g, '')
27
+ }
28
+
29
+ interface SidebarLinkProps {
30
+ readonly item: SidebarItem
31
+ }
32
+
33
+ function SidebarLink({ item }: SidebarLinkProps) {
34
+ const active = read(item.active)
35
+ const href = read(item.href) || '#'
36
+ return (
37
+ <a
38
+ href={href}
39
+ className={
40
+ active
41
+ ? 'block py-1.5 text-[13.5px] font-medium text-text-brand-primary border-l-2 border-border-brand pl-3 -ml-[2px]'
42
+ : 'block py-1.5 pl-3 -ml-[2px] text-[13.5px] text-gray-500 hover:text-gray-950 transition-colors'
43
+ }
44
+ >
45
+ <EditableRichText field={item.label} as="span" />
46
+ </a>
47
+ )
48
+ }
49
+
50
+ interface Props {
51
+ readonly sidebar: EditableSlot<'list', ReadonlyArray<SidebarGroup>>
52
+ readonly eyebrow: EditableSlot<'richText', string>
53
+ readonly title: EditableSlot<'richText', string>
54
+ readonly lead: EditableSlot<'richText', string>
55
+ readonly body: EditableSlot<'richText', string>
56
+ readonly prev: EditableSlot<'link', LinkValue>
57
+ readonly next: EditableSlot<'link', LinkValue>
58
+ }
59
+
60
+ interface TocEntry {
61
+ readonly id: string
62
+ readonly label: string
63
+ readonly level: 2 | 3
64
+ }
65
+
66
+ export function DocsArticleComponent({
67
+ sidebar,
68
+ eyebrow,
69
+ title,
70
+ lead,
71
+ body,
72
+ prev: rawPrev,
73
+ next: rawNext,
74
+ }: Props) {
75
+ const articleRef = useRef<HTMLDivElement>(null)
76
+ const [toc, setToc] = useState<ReadonlyArray<TocEntry>>([])
77
+
78
+ const prev = read(rawPrev)
79
+ const next = read(rawNext)
80
+ // Keep the prev/next nav slots clickable in preview even when their
81
+ // link is unconfigured — the `<EditableLink>` inside the `<a>` is the
82
+ // author's only entry point to the link picker. See
83
+ // `isSlotInPreview` JSDoc for the canonical idiom.
84
+ const showPrev = Boolean(hrefOf(prev)) || isSlotInPreview(rawPrev)
85
+ const showNext = Boolean(hrefOf(next)) || isSlotInPreview(rawNext)
86
+
87
+ useEffect(() => {
88
+ const root = articleRef.current
89
+ if (!root) return
90
+ const headings = root.querySelectorAll<HTMLElement>('h2, h3')
91
+ const used = new Set<string>()
92
+ const items: TocEntry[] = []
93
+ headings.forEach((h, i) => {
94
+ const label = (h.textContent || '').trim()
95
+ let id = slugify(label) || `section-${i}`
96
+ let n = 2
97
+ while (used.has(id)) {
98
+ id = `${slugify(label) || `section-${i}`}-${n++}`
99
+ }
100
+ used.add(id)
101
+ h.id = id
102
+ items.push({ id, label, level: h.tagName === 'H2' ? 2 : 3 })
103
+ })
104
+ setToc(items)
105
+ // The TOC depends on the rendered DOM, which depends on the body
106
+ // slot's underlying value. Pass `body` itself so React tracks slot
107
+ // identity changes (preview-mode revisions swap the slot reference).
108
+ }, [body])
109
+
110
+ return (
111
+ <section className="bg-gray-50 text-gray-950">
112
+ <div className="mx-auto w-full max-w-[1280px] px-6 lg:px-10">
113
+ <div className="grid grid-cols-1 gap-10 py-12 lg:grid-cols-[220px_minmax(0,1fr)_200px] lg:gap-14 lg:py-16">
114
+ {/* Sidebar */}
115
+ <aside className="hidden lg:block">
116
+ <div className="sticky top-10">
117
+ <EditableList
118
+ field={sidebar}
119
+ itemSchema={schema.sidebar.itemSchema}
120
+ className="flex flex-col gap-7"
121
+ renderItem={(group) => (
122
+ <div>
123
+ <EditableRichText
124
+ field={group.title}
125
+ as="div"
126
+ className="mb-2 text-[13px] font-medium text-gray-950"
127
+ />
128
+ <EditableList
129
+ field={group.items}
130
+ itemSchema={schema.sidebar.itemSchema.items.itemSchema}
131
+ className="flex flex-col border-l-[0.5px] border-gray-200"
132
+ renderItem={(item) => <SidebarLink item={item} />}
133
+ />
134
+ </div>
135
+ )}
136
+ />
137
+ </div>
138
+ </aside>
139
+
140
+ {/* Article */}
141
+ <article className="min-w-0 max-w-[760px]">
142
+ <EditableRichText
143
+ field={eyebrow}
144
+ as="div"
145
+ className="mb-4 text-[11px] font-medium uppercase tracking-[0.10em] text-text-brand-primary"
146
+ />
147
+ <EditableRichText
148
+ field={title}
149
+ className="
150
+ mb-5
151
+ [&_h1]:m-0 [&_h1]:font-display [&_h1]:font-medium [&_h1]:text-gray-950
152
+ [&_h1]:!text-[clamp(32px,4.5vw,44px)] [&_h1]:!leading-[1.06] [&_h1]:!tracking-[-0.03em]
153
+ "
154
+ />
155
+ <EditableRichText
156
+ field={lead}
157
+ className="
158
+ mb-12 max-w-[60ch]
159
+ [&_p]:m-0 [&_p]:text-[18px] [&_p]:leading-[1.6] [&_p]:text-gray-500
160
+ "
161
+ />
162
+
163
+ <div
164
+ ref={articleRef}
165
+ className="
166
+ [&_h2]:scroll-mt-24 [&_h2]:font-display [&_h2]:font-medium [&_h2]:text-gray-950
167
+ [&_h2]:!text-[26px] [&_h2]:!leading-[1.2] [&_h2]:!tracking-[-0.02em]
168
+ [&_h2]:mt-14 [&_h2]:mb-4 [&_h2]:pb-2 [&_h2]:border-b-[0.5px] [&_h2]:border-gray-200
169
+ [&_h3]:scroll-mt-24 [&_h3]:font-display [&_h3]:font-medium [&_h3]:text-gray-950
170
+ [&_h3]:!text-[19px] [&_h3]:!leading-[1.3] [&_h3]:!tracking-[-0.01em]
171
+ [&_h3]:mt-10 [&_h3]:mb-3
172
+ [&_p]:my-4 [&_p]:text-[15.5px] [&_p]:leading-[1.7] [&_p]:text-gray-950
173
+ [&_a]:text-text-brand-primary [&_a]:underline [&_a]:underline-offset-[3px] hover:[&_a]:no-underline
174
+ [&_strong]:font-medium [&_strong]:text-gray-950
175
+ [&_em]:italic
176
+ [&_ul]:my-4 [&_ul]:list-disc [&_ul]:pl-6
177
+ [&_ol]:my-4 [&_ol]:list-decimal [&_ol]:pl-6
178
+ [&_li]:my-1.5 [&_li]:text-[15.5px] [&_li]:leading-[1.7] [&_li]:text-gray-950
179
+ [&_li_p]:my-1
180
+ [&_code]:font-mono [&_code]:text-[13px] [&_code]:font-normal
181
+ [&_code]:text-text-brand-primary [&_code]:bg-bg-brand-primary
182
+ [&_code]:px-[6px] [&_code]:py-[2px] [&_code]:rounded-sm
183
+ [&_pre]:my-6 [&_pre]:bg-bg-brand-primary [&_pre]:rounded-lg
184
+ [&_pre]:px-5 [&_pre]:py-4 [&_pre]:overflow-x-auto
185
+ [&_pre]:font-mono [&_pre]:text-[13px] [&_pre]:leading-[1.7]
186
+ [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-text-primary [&_pre_code]:rounded-none
187
+ [&_blockquote]:my-6 [&_blockquote]:not-italic
188
+ [&_blockquote]:bg-white [&_blockquote]:border-l-2 [&_blockquote]:border-border-brand
189
+ [&_blockquote]:rounded-r-lg [&_blockquote]:px-5 [&_blockquote]:py-4
190
+ [&_blockquote_p]:m-0 [&_blockquote_p]:text-[15px] [&_blockquote_p]:leading-[1.65] [&_blockquote_p]:text-gray-950
191
+ [&_hr]:my-12 [&_hr]:border-0 [&_hr]:border-t-[0.5px] [&_hr]:border-gray-200
192
+ "
193
+ >
194
+ <EditableRichText field={body} />
195
+ </div>
196
+
197
+ {(showPrev || showNext) && (
198
+ <div className="mt-20 grid grid-cols-1 gap-3 border-t-[0.5px] border-gray-200 pt-10 sm:grid-cols-2">
199
+ {showPrev ? (
200
+ <a
201
+ href={hrefOf(prev) || '#'}
202
+ target={isExternalLink(prev) ? '_blank' : undefined}
203
+ rel={isExternalLink(prev) ? 'noreferrer' : undefined}
204
+ className="flex flex-col items-start gap-1 rounded-lg border-[0.5px] border-gray-200 bg-white px-5 py-4 transition-colors hover:border-border-brand"
205
+ >
206
+ <span className="text-[12px] text-gray-500">← Previous</span>
207
+ <EditableLink
208
+ field={rawPrev}
209
+ className="text-[15px] font-medium text-gray-950"
210
+ />
211
+ </a>
212
+ ) : (
213
+ <span />
214
+ )}
215
+ {showNext ? (
216
+ <a
217
+ href={hrefOf(next) || '#'}
218
+ target={isExternalLink(next) ? '_blank' : undefined}
219
+ rel={isExternalLink(next) ? 'noreferrer' : undefined}
220
+ className="flex flex-col items-end gap-1 rounded-lg border-[0.5px] border-gray-200 bg-white px-5 py-4 text-right transition-colors hover:border-border-brand sm:col-start-2"
221
+ >
222
+ <span className="text-[12px] text-gray-500">Next →</span>
223
+ <EditableLink
224
+ field={rawNext}
225
+ className="text-[15px] font-medium text-gray-950"
226
+ />
227
+ </a>
228
+ ) : (
229
+ <span />
230
+ )}
231
+ </div>
232
+ )}
233
+ </article>
234
+
235
+ {/* Right TOC */}
236
+ <aside className="hidden lg:block">
237
+ <div className="sticky top-10">
238
+ {toc.length > 0 && (
239
+ <>
240
+ <div className="mb-3 text-[11px] font-medium uppercase tracking-[0.10em] text-gray-500">
241
+ On this page
242
+ </div>
243
+ <ul className="flex flex-col">
244
+ {toc.map((entry) => (
245
+ <li
246
+ key={entry.id}
247
+ className={entry.level === 3 ? 'pl-3' : ''}
248
+ >
249
+ <a
250
+ href={`#${entry.id}`}
251
+ className="block py-1 text-[13px] text-gray-500 transition-colors hover:text-gray-950"
252
+ >
253
+ {entry.label}
254
+ </a>
255
+ </li>
256
+ ))}
257
+ </ul>
258
+ </>
259
+ )}
260
+ </div>
261
+ </aside>
262
+ </div>
263
+ </div>
264
+ </section>
265
+ )
266
+ }