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.
- package/README.md +39 -0
- package/dist/index.mjs +297 -0
- package/dist/template/.claude/settings.json +6 -0
- package/dist/template/.claude/skills/.gitkeep +0 -0
- package/dist/template/.claude-plugin/channel/server.mjs +254 -0
- package/dist/template/.claude-plugin/channel/server.ts +369 -0
- package/dist/template/.claude-plugin/plugin.json +17 -0
- package/dist/template/.mcp.json +8 -0
- package/dist/template/BRAND.md +49 -0
- package/dist/template/CLAUDE.md +157 -0
- package/dist/template/agntcms/config.ts +49 -0
- package/dist/template/agntcms/sections/ArticleBody/component.tsx +32 -0
- package/dist/template/agntcms/sections/ArticleBody/index.ts +10 -0
- package/dist/template/agntcms/sections/ArticleBody/schema.ts +5 -0
- package/dist/template/agntcms/sections/ArticleHero/component.tsx +87 -0
- package/dist/template/agntcms/sections/ArticleHero/index.ts +10 -0
- package/dist/template/agntcms/sections/ArticleHero/schema.ts +12 -0
- package/dist/template/agntcms/sections/Banner/component.tsx +83 -0
- package/dist/template/agntcms/sections/Banner/index.ts +10 -0
- package/dist/template/agntcms/sections/Banner/schema.ts +9 -0
- package/dist/template/agntcms/sections/BlogIndex/component.tsx +173 -0
- package/dist/template/agntcms/sections/BlogIndex/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogIndex/schema.ts +33 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/component.tsx +44 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogIndexHeader/schema.ts +8 -0
- package/dist/template/agntcms/sections/BlogPostBody/component.tsx +50 -0
- package/dist/template/agntcms/sections/BlogPostBody/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostBody/schema.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostHero/component.tsx +88 -0
- package/dist/template/agntcms/sections/BlogPostHero/index.ts +10 -0
- package/dist/template/agntcms/sections/BlogPostHero/schema.ts +35 -0
- package/dist/template/agntcms/sections/CaseStudies/component.tsx +92 -0
- package/dist/template/agntcms/sections/CaseStudies/index.ts +10 -0
- package/dist/template/agntcms/sections/CaseStudies/schema.ts +17 -0
- package/dist/template/agntcms/sections/ContactForm/component.tsx +119 -0
- package/dist/template/agntcms/sections/ContactForm/index.ts +10 -0
- package/dist/template/agntcms/sections/ContactForm/schema.ts +15 -0
- package/dist/template/agntcms/sections/DocsArticle/component.tsx +266 -0
- package/dist/template/agntcms/sections/DocsArticle/index.ts +10 -0
- package/dist/template/agntcms/sections/DocsArticle/schema.ts +33 -0
- package/dist/template/agntcms/sections/FAQ/component.tsx +57 -0
- package/dist/template/agntcms/sections/FAQ/index.ts +10 -0
- package/dist/template/agntcms/sections/FAQ/schema.ts +11 -0
- package/dist/template/agntcms/sections/FeatureGrid/component.tsx +117 -0
- package/dist/template/agntcms/sections/FeatureGrid/index.ts +10 -0
- package/dist/template/agntcms/sections/FeatureGrid/schema.ts +21 -0
- package/dist/template/agntcms/sections/FeaturedArticles/component.tsx +99 -0
- package/dist/template/agntcms/sections/FeaturedArticles/index.ts +10 -0
- package/dist/template/agntcms/sections/FeaturedArticles/schema.ts +17 -0
- package/dist/template/agntcms/sections/GettingStarted/component.tsx +116 -0
- package/dist/template/agntcms/sections/GettingStarted/index.ts +10 -0
- package/dist/template/agntcms/sections/GettingStarted/schema.ts +11 -0
- package/dist/template/agntcms/sections/Hero/component.tsx +148 -0
- package/dist/template/agntcms/sections/Hero/index.ts +10 -0
- package/dist/template/agntcms/sections/Hero/schema.ts +16 -0
- package/dist/template/agntcms/sections/HowItWorks/component.tsx +57 -0
- package/dist/template/agntcms/sections/HowItWorks/index.ts +10 -0
- package/dist/template/agntcms/sections/HowItWorks/schema.ts +11 -0
- package/dist/template/agntcms/sections/ImageText/component.tsx +110 -0
- package/dist/template/agntcms/sections/ImageText/index.ts +10 -0
- package/dist/template/agntcms/sections/ImageText/schema.ts +14 -0
- package/dist/template/agntcms/sections/LogoStrip/component.tsx +37 -0
- package/dist/template/agntcms/sections/LogoStrip/index.ts +10 -0
- package/dist/template/agntcms/sections/LogoStrip/schema.ts +6 -0
- package/dist/template/agntcms/sections/Newsletter/component.tsx +48 -0
- package/dist/template/agntcms/sections/Newsletter/index.ts +10 -0
- package/dist/template/agntcms/sections/Newsletter/schema.ts +8 -0
- package/dist/template/agntcms/sections/OpenSource/component.tsx +99 -0
- package/dist/template/agntcms/sections/OpenSource/index.ts +10 -0
- package/dist/template/agntcms/sections/OpenSource/schema.ts +13 -0
- package/dist/template/agntcms/sections/PainAnswer/component.tsx +81 -0
- package/dist/template/agntcms/sections/PainAnswer/index.ts +10 -0
- package/dist/template/agntcms/sections/PainAnswer/schema.ts +15 -0
- package/dist/template/agntcms/sections/PricingPlans/component.tsx +100 -0
- package/dist/template/agntcms/sections/PricingPlans/index.ts +10 -0
- package/dist/template/agntcms/sections/PricingPlans/schema.ts +13 -0
- package/dist/template/agntcms/sections/Problem/component.tsx +49 -0
- package/dist/template/agntcms/sections/Problem/index.ts +10 -0
- package/dist/template/agntcms/sections/Problem/schema.ts +12 -0
- package/dist/template/agntcms/sections/SiteFooter/component.tsx +88 -0
- package/dist/template/agntcms/sections/SiteFooter/index.ts +10 -0
- package/dist/template/agntcms/sections/SiteFooter/schema.ts +13 -0
- package/dist/template/agntcms/sections/SiteHeader/component.tsx +99 -0
- package/dist/template/agntcms/sections/SiteHeader/index.ts +10 -0
- package/dist/template/agntcms/sections/SiteHeader/schema.ts +14 -0
- package/dist/template/agntcms/sections/SiteMeta/component.tsx +26 -0
- package/dist/template/agntcms/sections/SiteMeta/index.ts +13 -0
- package/dist/template/agntcms/sections/SiteMeta/schema.ts +18 -0
- package/dist/template/agntcms/sections/TabbedFeatures/component.tsx +120 -0
- package/dist/template/agntcms/sections/TabbedFeatures/index.ts +10 -0
- package/dist/template/agntcms/sections/TabbedFeatures/schema.ts +13 -0
- package/dist/template/agntcms/sections/TeamGrid/component.tsx +77 -0
- package/dist/template/agntcms/sections/TeamGrid/index.ts +10 -0
- package/dist/template/agntcms/sections/TeamGrid/schema.ts +14 -0
- package/dist/template/agntcms/sections/Testimonials/component.tsx +76 -0
- package/dist/template/agntcms/sections/Testimonials/index.ts +10 -0
- package/dist/template/agntcms/sections/Testimonials/schema.ts +12 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/component.tsx +86 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/index.ts +10 -0
- package/dist/template/agntcms/sections/WhatIsBuilt/schema.ts +20 -0
- package/dist/template/agntcms/site-meta.ts +81 -0
- package/dist/template/app/[[...slug]]/page.tsx +123 -0
- package/dist/template/app/admin/AdminPageClient.tsx +77 -0
- package/dist/template/app/admin/AdminPageDynamic.tsx +24 -0
- package/dist/template/app/admin/page.tsx +14 -0
- package/dist/template/app/api/agntcms/_shared.ts +80 -0
- package/dist/template/app/api/agntcms/assets/route.ts +11 -0
- package/dist/template/app/api/agntcms/assets/upload/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/discard/route.ts +12 -0
- package/dist/template/app/api/agntcms/draft/list/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/publish/route.ts +11 -0
- package/dist/template/app/api/agntcms/draft/reorder/route.ts +10 -0
- package/dist/template/app/api/agntcms/draft/save/route.ts +11 -0
- package/dist/template/app/api/agntcms/events/route.ts +12 -0
- package/dist/template/app/api/agntcms/forms/delete/route.ts +17 -0
- package/dist/template/app/api/agntcms/forms/list/route.ts +24 -0
- package/dist/template/app/api/agntcms/forms/read/route.ts +23 -0
- package/dist/template/app/api/agntcms/forms/submit/route.ts +17 -0
- package/dist/template/app/api/agntcms/global/delete/route.ts +13 -0
- package/dist/template/app/api/agntcms/global/history/route.ts +10 -0
- package/dist/template/app/api/agntcms/global/list/route.ts +14 -0
- package/dist/template/app/api/agntcms/global/read/route.ts +11 -0
- package/dist/template/app/api/agntcms/global/rollback/route.ts +10 -0
- package/dist/template/app/api/agntcms/global/save/route.ts +14 -0
- package/dist/template/app/api/agntcms/mcp/route.ts +12 -0
- package/dist/template/app/api/agntcms/page/delete/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/duplicate/route.ts +11 -0
- package/dist/template/app/api/agntcms/page/history/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/list/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/read/route.ts +11 -0
- package/dist/template/app/api/agntcms/page/rename/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/rollback/route.ts +10 -0
- package/dist/template/app/api/agntcms/page/unpublish/route.ts +11 -0
- package/dist/template/app/api/agntcms/preview/enter/route.ts +13 -0
- package/dist/template/app/api/agntcms/preview/exit/route.ts +10 -0
- package/dist/template/app/api/agntcms/preview/issue/route.ts +12 -0
- package/dist/template/app/api/agntcms/template/list/route.ts +15 -0
- package/dist/template/app/apple-icon.svg +9 -0
- package/dist/template/app/icon.svg +9 -0
- package/dist/template/app/layout.tsx +107 -0
- package/dist/template/app/not-found.tsx +75 -0
- package/dist/template/app/robots.ts +33 -0
- package/dist/template/app/sitemap.ts +49 -0
- package/dist/template/content/globals/site-footer.json +53 -0
- package/dist/template/content/globals/site-header.json +18 -0
- package/dist/template/content/globals/site-meta.json +13 -0
- package/dist/template/content/pages/404.json +34 -0
- package/dist/template/content/pages/about.json +307 -0
- package/dist/template/content/pages/article-editor.json +61 -0
- package/dist/template/content/pages/article-schemas.json +61 -0
- package/dist/template/content/pages/blog.json +162 -0
- package/dist/template/content/pages/contact.json +29 -0
- package/dist/template/content/pages/home.json +243 -0
- package/dist/template/content/pages/pricing.json +219 -0
- package/dist/template/content/pages/services.json +177 -0
- package/dist/template/fonts/Satoshi-Medium.woff2 +0 -0
- package/dist/template/fonts/Satoshi-Regular.woff2 +0 -0
- package/dist/template/next.config.ts +6 -0
- package/dist/template/package.json +36 -0
- package/dist/template/postcss.config.mjs +5 -0
- package/dist/template/public/assets/.gitkeep +0 -0
- package/dist/template/public/assets/0418d7ed21f57e7b9e0546725c92b8419daeaa355675d9070fab0c2013cf1524.jpg +0 -0
- package/dist/template/public/assets/0d0475f21aa96435a8ed3cdb2fddcc6278492e76ae842f569432454f4d33631a.jpg +0 -0
- package/dist/template/public/assets/27457a1adee2372030d9876b0d52c44d46be98843999935eaef2526b9b961f12.jpg +0 -0
- package/dist/template/public/assets/3855d91192f0c6120b01427b78ef84e52baa9f4b5a17d4271e41c1bfd95a5b0c.jpg +0 -0
- package/dist/template/public/assets/3b3b90c5084635b746be673ede92a328f002f5621a42c9a5cb89c5e2435652cb.jpg +0 -0
- package/dist/template/public/assets/3e76165a78fd3e7b8ed1e93dee50803ae11110c756c8c1c89229a2dec2bc0abf.jpg +0 -0
- package/dist/template/public/assets/4a3e28f85dc850c347ea0fd931696aa936a6bd45f193e7f1c9328b5896fb272c.jpg +0 -0
- package/dist/template/public/assets/579f67d5fd4c9106c6cdf2ef29f50df934ad0fc2b7849bac1e1cfb1e3f92303b.jpg +0 -0
- package/dist/template/public/assets/5b95209269661bb60fb250f1da682e05b9efa64dd42f350608b299e6bf1f2f35.jpg +0 -0
- package/dist/template/public/assets/5e04b46f8317ef95a7ddf85aedfe5c098a755f05056325d0251eccf95ce51172.jpg +0 -0
- package/dist/template/public/assets/6167a9164be2cf1183bdfdd4946bf9b908570e79e92a2380c25f0bb702422bbd.jpg +0 -0
- package/dist/template/public/assets/75e723ec316de28247924e5dfb73a4b266e10de605e749f150883d280ed8ed16.jpg +0 -0
- package/dist/template/public/assets/816a11e6a7245feaf51bbebf09d1bda3f125b334bc24fc3b8f47b5380a7b4294.jpg +0 -0
- package/dist/template/public/assets/81eba6f5654b8746a9b0cba1a9521a67f2b4afaaefc7c88d66dfab1461270d8f.jpg +0 -0
- package/dist/template/public/assets/82a2ce9e49361098f77a28755779dc5a7c026831cbd135175749c1304e21dacc.jpg +0 -0
- package/dist/template/public/assets/8d7b02ba277ba56bdafdbd47b01f7df6d993c714b4dc2305eb65a1307c09647d.jpg +0 -0
- package/dist/template/public/assets/b303185b471678e4d62f678a1549ee26022f4745407d08cae44ecb1c25352293.jpg +0 -0
- package/dist/template/public/assets/b69b49169c11546100d6dd5280073bc0d84cbbcc6d33fa01ecf6a5866fa42237.jpg +0 -0
- package/dist/template/public/assets/c4d2f0d1a310e457ac722a399693652e3c86c55b294243d5ffc679394e12f9d1.jpg +0 -0
- package/dist/template/public/assets/cae09f4729f8a348b67267c2f2a550be0f3bfa420689afe1a5cf8b7e2b146238.png +0 -0
- package/dist/template/public/assets/cb3acf58b57417a4b26474ba04c096af7103c4320ed2f4f3683f79d7670a055c.jpg +0 -0
- package/dist/template/public/assets/d5a0701b2d156284e0ce851cd2534ec632db34f91fbcbee3b8a7784d45ce78d2.jpg +0 -0
- package/dist/template/public/assets/d6ef1c3f48b0e488521794fb60701da1fd2c3a1621d6ac5f17ccfd4909d3be60.jpg +0 -0
- package/dist/template/public/assets/de249ff9be2539cf0d1ce092de3c57001839b6c3e14fcee3fc31a7b7673ae007.jpg +0 -0
- package/dist/template/public/assets/eac45438956be187b010e24b3289757aa00f227c190d49ee99fea510552dd2ba.jpg +0 -0
- package/dist/template/public/assets/f8b9200065b5436c6a88361839edc2b89be88d3037c84a80d3ee95c32891510b.jpg +0 -0
- package/dist/template/public/assets/placeholder.png +0 -0
- package/dist/template/public/brand/mark.svg +6 -0
- package/dist/template/public/brand/wordmark-light.svg +6 -0
- package/dist/template/public/brand/wordmark.svg +6 -0
- package/dist/template/styles/globals.css +69 -0
- package/dist/template/styles/theme.css +492 -0
- package/dist/template/styles/typography.css +469 -0
- package/dist/template/tsconfig.json +30 -0
- 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
|
+
}
|