@pylonsync/create-pylon 0.11.1 → 0.11.4
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/bin/create-pylon.js +1 -1
- package/package.json +1 -1
- package/templates/_root/AGENTS.md +1 -1
- package/templates/agency/AGENTS.md +1 -1
- package/templates/agency/app/(marketing)/contact-form.tsx +22 -32
- package/templates/agency/app/(marketing)/layout.tsx +36 -64
- package/templates/agency/app/(marketing)/not-found.tsx +6 -8
- package/templates/agency/app/(marketing)/page.tsx +93 -144
- package/templates/agency/app/(marketing)/work/[slug]/page.tsx +69 -71
- package/templates/agency/app/(marketing)/work/page.tsx +20 -17
- package/templates/agency/app/globals.css +15 -6
- package/templates/agency/app.ts +10 -0
- package/templates/agency/components/marketing.tsx +40 -104
- package/templates/agency/lib/site.config.ts +34 -52
- package/templates/agency/public/images/hero.jpg +0 -0
- package/templates/agency/public/images/team/claire-donovan.jpg +0 -0
- package/templates/agency/public/images/team/dana-okafor.jpg +0 -0
- package/templates/agency/public/images/team/marcus-lee.jpg +0 -0
- package/templates/agency/public/images/work/atlas-health.jpg +0 -0
- package/templates/agency/public/images/work/cohort.jpg +0 -0
- package/templates/agency/public/images/work/ledger.jpg +0 -0
- package/templates/agency/public/images/work/vela.jpg +0 -0
- package/templates/ai-chat/AGENTS.md +1 -1
- package/templates/ai-studio/AGENTS.md +1 -1
- package/templates/barebones/AGENTS.md +1 -1
- package/templates/chat/AGENTS.md +1 -1
- package/templates/consumer/AGENTS.md +1 -1
- package/templates/consumer/README.md +39 -23
- package/templates/consumer/app/(app)/explore/page.tsx +13 -0
- package/templates/consumer/app/(app)/layout.tsx +17 -0
- package/templates/consumer/app/(app)/p/[id]/page.tsx +13 -0
- package/templates/consumer/app/(app)/page.tsx +15 -0
- package/templates/consumer/app/(app)/u/[username]/page.tsx +14 -0
- package/templates/consumer/app/error.tsx +1 -1
- package/templates/consumer/app/globals.css +25 -0
- package/templates/consumer/app/layout.tsx +3 -10
- package/templates/consumer/app/llms.ts +10 -10
- package/templates/consumer/app/not-found.tsx +9 -22
- package/templates/consumer/app/robots.ts +3 -4
- package/templates/consumer/app/sitemap.ts +7 -21
- package/templates/consumer/app.ts +152 -33
- package/templates/consumer/components/social/app-chrome.tsx +109 -0
- package/templates/consumer/components/social/avatar.tsx +50 -0
- package/templates/consumer/components/social/comment-form.tsx +51 -0
- package/templates/consumer/components/social/create-post.tsx +155 -0
- package/templates/consumer/components/social/edit-profile.tsx +103 -0
- package/templates/consumer/components/social/explore-screen.tsx +23 -0
- package/templates/consumer/components/social/home-screen.tsx +161 -0
- package/templates/consumer/components/social/post-card.tsx +77 -0
- package/templates/consumer/components/social/post-grid.tsx +46 -0
- package/templates/consumer/components/social/post-parts.tsx +146 -0
- package/templates/consumer/components/social/post-screen.tsx +173 -0
- package/templates/consumer/components/social/profile-screen.tsx +146 -0
- package/templates/consumer/components/social/rich-text.tsx +22 -0
- package/templates/consumer/components/social/session.tsx +43 -0
- package/templates/consumer/components/social/use-social.ts +86 -0
- package/templates/consumer/functions/addComment.ts +27 -0
- package/templates/consumer/functions/createPost.ts +39 -0
- package/templates/consumer/functions/ensureProfile.ts +39 -0
- package/templates/consumer/functions/seedFeed.ts +38 -0
- package/templates/consumer/functions/updateProfile.ts +61 -0
- package/templates/consumer/lib/seed.ts +192 -0
- package/templates/consumer/lib/site.ts +6 -0
- package/templates/consumer/lib/social.ts +245 -0
- package/templates/consumer/tests/social.test.ts +157 -0
- package/templates/creator/AGENTS.md +1 -1
- package/templates/creator/app/(marketing)/layout.tsx +35 -69
- package/templates/creator/app/(marketing)/newsletter-signup.tsx +15 -26
- package/templates/creator/app/(marketing)/not-found.tsx +8 -6
- package/templates/creator/app/(marketing)/page.tsx +90 -106
- package/templates/creator/app/globals.css +14 -0
- package/templates/creator/app/layout.tsx +1 -0
- package/templates/creator/app.ts +10 -0
- package/templates/creator/components/marketing.tsx +28 -89
- package/templates/creator/lib/site.config.ts +51 -29
- package/templates/creator/public/images/headshot.jpg +0 -0
- package/templates/crm/AGENTS.md +1 -1
- package/templates/crm/lib/seed.ts +153 -21
- package/templates/directory/AGENTS.md +1 -1
- package/templates/directory/app/(marketing)/directory-browse.tsx +99 -137
- package/templates/directory/app/(marketing)/layout.tsx +37 -88
- package/templates/directory/app/(marketing)/not-found.tsx +11 -10
- package/templates/directory/app/(marketing)/page.tsx +12 -33
- package/templates/directory/app/(marketing)/submit/page.tsx +9 -10
- package/templates/directory/app/(marketing)/submit-form.tsx +26 -26
- package/templates/directory/app/globals.css +15 -4
- package/templates/directory/app.ts +10 -0
- package/templates/directory/components/marketing.tsx +27 -91
- package/templates/directory/lib/site.config.ts +13 -24
- package/templates/helpdesk/AGENTS.md +1 -1
- package/templates/helpdesk/lib/seed.ts +188 -104
- package/templates/inventory/AGENTS.md +1 -1
- package/templates/inventory/lib/seed.ts +175 -44
- package/templates/invoices/AGENTS.md +1 -1
- package/templates/invoices/lib/seed.ts +74 -89
- package/templates/local-service/AGENTS.md +1 -1
- package/templates/local-service/app/(marketing)/booking-widget.tsx +31 -31
- package/templates/local-service/app/(marketing)/layout.tsx +25 -37
- package/templates/local-service/app/(marketing)/not-found.tsx +4 -4
- package/templates/local-service/app/(marketing)/page.tsx +145 -139
- package/templates/local-service/app/globals.css +11 -0
- package/templates/local-service/app/layout.tsx +2 -0
- package/templates/local-service/app.ts +9 -0
- package/templates/local-service/components/marketing.tsx +14 -39
- package/templates/local-service/lib/site.config.ts +16 -23
- package/templates/local-service/public/images/hero.jpg +0 -0
- package/templates/marketplace/AGENTS.md +1 -1
- package/templates/marketplace/client/SellForm.tsx +2 -0
- package/templates/projects/AGENTS.md +1 -1
- package/templates/projects/lib/seed.ts +103 -32
- package/templates/restaurant/AGENTS.md +1 -1
- package/templates/restaurant/app/(marketing)/layout.tsx +26 -48
- package/templates/restaurant/app/(marketing)/page.tsx +146 -123
- package/templates/restaurant/app/globals.css +11 -0
- package/templates/restaurant/app/layout.tsx +1 -0
- package/templates/restaurant/app.ts +10 -0
- package/templates/restaurant/components/marketing.tsx +10 -0
- package/templates/restaurant/lib/site.config.ts +16 -14
- package/templates/restaurant/public/images/bar.jpg +0 -0
- package/templates/restaurant/public/images/branzino.jpg +0 -0
- package/templates/restaurant/public/images/hearth.jpg +0 -0
- package/templates/restaurant/public/images/hero.jpg +0 -0
- package/templates/saas/AGENTS.md +1 -1
- package/templates/shop/AGENTS.md +1 -1
- package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
- package/templates/shop/public/images/products/linen.jpg +0 -0
- package/templates/todo/AGENTS.md +1 -1
- package/templates/waitlist/AGENTS.md +1 -1
- package/templates/waitlist/app/(marketing)/layout.tsx +39 -85
- package/templates/waitlist/app/(marketing)/not-found.tsx +8 -9
- package/templates/waitlist/app/(marketing)/page.tsx +51 -86
- package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +43 -83
- package/templates/waitlist/app/globals.css +23 -0
- package/templates/waitlist/app/layout.tsx +1 -0
- package/templates/waitlist/app.ts +18 -0
- package/templates/waitlist/components/marketing.tsx +67 -81
- package/templates/waitlist/lib/site.config.ts +52 -59
- package/templates/consumer/app/feed-client.tsx +0 -159
- package/templates/consumer/app/page.tsx +0 -27
- package/templates/consumer/functions/_keep.ts +0 -13
- package/templates/consumer/functions/seedPosts.ts +0 -56
- package/templates/consumer/tests/example.test.ts +0 -12
|
@@ -4,19 +4,21 @@ import { Link, type NotFoundProps } from "@pylonsync/react";
|
|
|
4
4
|
// `(marketing)/not-found.tsx` → rendered at HTTP 404 for any unmatched URL
|
|
5
5
|
// (and when a page calls `response.notFound()`). It lives inside the
|
|
6
6
|
// `(marketing)` group on purpose: a group segment adds no URL prefix, so this
|
|
7
|
-
// is still the root 404 boundary, but it wraps in the marketing layout
|
|
7
|
+
// is still the root 404 boundary, but it wraps in the marketing layout, so a
|
|
8
8
|
// missing URL gets the site nav + footer instead of a bare page. Hydrated, so
|
|
9
9
|
// the link is a client nav.
|
|
10
10
|
export default function NotFound(_props: NotFoundProps) {
|
|
11
11
|
return (
|
|
12
|
-
<div className="mx-auto
|
|
13
|
-
<h1 className="text-
|
|
14
|
-
<p className="mt-
|
|
12
|
+
<div className="mx-auto w-full max-w-[40rem] px-6 py-24">
|
|
13
|
+
<h1 className="text-[2rem] font-medium leading-tight text-ink">Page not found</h1>
|
|
14
|
+
<p className="mt-4 text-[17.5px] leading-[1.65] text-ink-2">
|
|
15
|
+
There is nothing at this address.
|
|
16
|
+
</p>
|
|
15
17
|
<Link
|
|
16
18
|
href="/"
|
|
17
|
-
className="mt-6 inline-
|
|
19
|
+
className="mt-6 inline-block text-[17.5px] font-medium text-ink underline decoration-brand/50 underline-offset-4 hover:decoration-brand"
|
|
18
20
|
>
|
|
19
|
-
Back home
|
|
21
|
+
Back to the home page
|
|
20
22
|
</Link>
|
|
21
23
|
</div>
|
|
22
24
|
);
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { type Metadata } from "@pylonsync/react";
|
|
3
|
-
import {
|
|
4
|
-
WRAP,
|
|
5
|
-
Eyebrow,
|
|
6
|
-
Divider,
|
|
7
|
-
SectionHead,
|
|
8
|
-
initials,
|
|
9
|
-
ImagePlaceholder,
|
|
10
|
-
} from "@/components/marketing";
|
|
3
|
+
import { WRAP, PROSE, Divider, SectionTitle, ImagePlaceholder } from "@/components/marketing";
|
|
11
4
|
import { NewsletterSignup } from "./newsletter-signup";
|
|
12
5
|
import { siteConfig } from "@/lib/site.config";
|
|
13
6
|
|
|
@@ -17,97 +10,105 @@ export const metadata: Metadata = {
|
|
|
17
10
|
openGraph: { title: siteConfig.seo.title, description: siteConfig.seo.description, type: "website" },
|
|
18
11
|
};
|
|
19
12
|
|
|
20
|
-
// `app/page.tsx` → `/`. A server-rendered personal
|
|
21
|
-
// offerings, testimonials, and
|
|
22
|
-
//
|
|
23
|
-
// subscriber counter. All copy comes
|
|
24
|
-
// the public page stays cacheable.
|
|
13
|
+
// `app/page.tsx` → `/`. A server-rendered personal site set in one narrow
|
|
14
|
+
// column. Hero, about, offerings, writing, testimonials, and the closing
|
|
15
|
+
// section are static server HTML (SEO + first paint); the newsletter form in
|
|
16
|
+
// the hero is a client island with a live subscriber counter. All copy comes
|
|
17
|
+
// from siteConfig. Doesn't read `auth`, so the public page stays cacheable.
|
|
25
18
|
export default function LandingPage() {
|
|
26
|
-
const { hero, about, offerings, testimonials, newsletter, links } = siteConfig;
|
|
19
|
+
const { hero, about, offerings, writing, testimonials, newsletter, links } = siteConfig;
|
|
27
20
|
|
|
28
21
|
return (
|
|
29
|
-
|
|
22
|
+
<>
|
|
30
23
|
{/* HERO */}
|
|
31
|
-
<section className={`${WRAP} pt-
|
|
32
|
-
<div className="
|
|
24
|
+
<section className={`${WRAP} pt-14 pb-12 sm:pt-20`}>
|
|
25
|
+
<div className="flex items-center gap-5">
|
|
26
|
+
<div className="w-20 shrink-0 sm:w-24">
|
|
27
|
+
<ImagePlaceholder shape="circle" title={hero.name} src="/images/headshot.jpg" />
|
|
28
|
+
</div>
|
|
33
29
|
<div>
|
|
34
|
-
<
|
|
35
|
-
<span className="inline-block size-1.5 rounded-full bg-brand" />
|
|
36
|
-
{hero.badge}
|
|
37
|
-
</span>
|
|
38
|
-
<h1 className="mt-5 text-balance text-[2.5rem] font-semibold leading-[1.04] tracking-[-0.02em] sm:text-[3rem]">
|
|
30
|
+
<h1 className="text-[2rem] font-medium leading-[1.1] text-ink sm:text-[2.5rem]">
|
|
39
31
|
{hero.name}
|
|
40
32
|
</h1>
|
|
41
|
-
<p className="mt-
|
|
42
|
-
<p className="mt-5 max-w-xl text-[16px] leading-relaxed text-zinc-500">{hero.intro}</p>
|
|
43
|
-
<div className="mt-7 flex flex-wrap items-center gap-4">
|
|
44
|
-
<a href="#newsletter" className="inline-flex items-center rounded-full bg-zinc-900 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-zinc-700">
|
|
45
|
-
Read the newsletter
|
|
46
|
-
</a>
|
|
47
|
-
<a href="#work" className="text-sm font-medium text-zinc-700 hover:text-zinc-900">
|
|
48
|
-
Work with me →
|
|
49
|
-
</a>
|
|
50
|
-
</div>
|
|
33
|
+
<p className="mt-1.5 text-[17.5px] italic leading-snug text-ink-2">{hero.tagline}</p>
|
|
51
34
|
</div>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
</div>
|
|
36
|
+
<p className={`mt-8 ${PROSE}`}>{hero.intro}</p>
|
|
37
|
+
<div id="newsletter" className="mt-8 scroll-mt-20">
|
|
38
|
+
<p className={PROSE}>
|
|
39
|
+
<em>{newsletter.name}</em> is my newsletter. {newsletter.subcopy}
|
|
40
|
+
</p>
|
|
41
|
+
<div className="mt-4">
|
|
42
|
+
<NewsletterSignup newsletter={newsletter} />
|
|
59
43
|
</div>
|
|
60
44
|
</div>
|
|
61
45
|
</section>
|
|
62
46
|
|
|
63
47
|
{/* ABOUT */}
|
|
64
48
|
<Divider />
|
|
65
|
-
<section className={`${WRAP} py-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
))}
|
|
72
|
-
</div>
|
|
49
|
+
<section className={`${WRAP} py-12`}>
|
|
50
|
+
<SectionTitle>{about.headline}</SectionTitle>
|
|
51
|
+
<div className={`mt-5 space-y-5 ${PROSE}`}>
|
|
52
|
+
{about.paragraphs.map((p) => (
|
|
53
|
+
<p key={p}>{p}</p>
|
|
54
|
+
))}
|
|
73
55
|
</div>
|
|
74
56
|
</section>
|
|
75
57
|
|
|
76
|
-
{/*
|
|
58
|
+
{/* WHAT I DO */}
|
|
77
59
|
<Divider />
|
|
78
|
-
<section
|
|
79
|
-
<
|
|
80
|
-
<
|
|
60
|
+
<section className={`${WRAP} py-12`}>
|
|
61
|
+
<SectionTitle>{offerings.headline}</SectionTitle>
|
|
62
|
+
<dl className="mt-5 divide-y divide-rule">
|
|
81
63
|
{offerings.items.map((o) => (
|
|
82
|
-
<div key={o.title} className="
|
|
83
|
-
<
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
64
|
+
<div key={o.title} className="py-5 first:pt-0 last:pb-0">
|
|
65
|
+
<dt className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1">
|
|
66
|
+
<span className="text-[18px] font-medium text-ink">{o.title}</span>
|
|
67
|
+
{o.price ? <span className="text-[16px] italic text-brand">{o.price}</span> : null}
|
|
68
|
+
</dt>
|
|
69
|
+
<dd className={`mt-1.5 ${PROSE}`}>{o.body}</dd>
|
|
88
70
|
</div>
|
|
89
71
|
))}
|
|
90
|
-
</
|
|
72
|
+
</dl>
|
|
91
73
|
</section>
|
|
92
74
|
|
|
75
|
+
{/* RECENT WRITING */}
|
|
76
|
+
{writing && writing.items.length > 0 ? (
|
|
77
|
+
<>
|
|
78
|
+
<Divider />
|
|
79
|
+
<section className={`${WRAP} py-12`}>
|
|
80
|
+
<SectionTitle>{writing.headline}</SectionTitle>
|
|
81
|
+
<ul className="mt-5 divide-y divide-rule">
|
|
82
|
+
{writing.items.map((issue) => (
|
|
83
|
+
<li key={issue.title} className="py-4 first:pt-0 last:pb-0">
|
|
84
|
+
<a href={issue.href} className="group block">
|
|
85
|
+
<div className="flex flex-wrap items-baseline justify-between gap-x-6 gap-y-1">
|
|
86
|
+
<span className="text-[18px] font-medium text-ink underline-offset-4 group-hover:underline">
|
|
87
|
+
{issue.title}
|
|
88
|
+
</span>
|
|
89
|
+
<time className="text-[15px] text-ink-2">{issue.date}</time>
|
|
90
|
+
</div>
|
|
91
|
+
{issue.summary ? <p className={`mt-1 ${PROSE}`}>{issue.summary}</p> : null}
|
|
92
|
+
</a>
|
|
93
|
+
</li>
|
|
94
|
+
))}
|
|
95
|
+
</ul>
|
|
96
|
+
</section>
|
|
97
|
+
</>
|
|
98
|
+
) : null}
|
|
99
|
+
|
|
93
100
|
{/* TESTIMONIALS */}
|
|
94
101
|
{testimonials && testimonials.items.length > 0 ? (
|
|
95
102
|
<>
|
|
96
103
|
<Divider />
|
|
97
|
-
<section className={`${WRAP} py-
|
|
98
|
-
<
|
|
99
|
-
<div className="mt-
|
|
104
|
+
<section className={`${WRAP} py-12`}>
|
|
105
|
+
<SectionTitle>{testimonials.headline}</SectionTitle>
|
|
106
|
+
<div className="mt-5 space-y-7">
|
|
100
107
|
{testimonials.items.map((t) => (
|
|
101
|
-
<figure key={t.name} className="
|
|
102
|
-
<blockquote className=
|
|
103
|
-
<figcaption className="mt-
|
|
104
|
-
|
|
105
|
-
{initials(t.name)}
|
|
106
|
-
</span>
|
|
107
|
-
<div className="leading-tight">
|
|
108
|
-
<div className="text-[13px] font-semibold">{t.name}</div>
|
|
109
|
-
<div className="text-[12px] text-zinc-500">{t.role}</div>
|
|
110
|
-
</div>
|
|
108
|
+
<figure key={t.name} className="border-l-2 border-brand pl-5">
|
|
109
|
+
<blockquote className={`italic ${PROSE}`}>“{t.quote}”</blockquote>
|
|
110
|
+
<figcaption className="mt-2 text-[15px] text-ink-2">
|
|
111
|
+
{t.name}, {t.role}
|
|
111
112
|
</figcaption>
|
|
112
113
|
</figure>
|
|
113
114
|
))}
|
|
@@ -116,45 +117,28 @@ export default function LandingPage() {
|
|
|
116
117
|
</>
|
|
117
118
|
) : null}
|
|
118
119
|
|
|
119
|
-
{/*
|
|
120
|
-
|
|
121
|
-
<section id="newsletter" className={`${WRAP} py-14`}>
|
|
122
|
-
<div className="rounded-2xl border border-zinc-200 bg-paper p-7 sm:p-10">
|
|
123
|
-
<Eyebrow>{newsletter.eyebrow}</Eyebrow>
|
|
124
|
-
<h2 className="mt-4 max-w-2xl text-balance text-2xl font-semibold leading-[1.15] tracking-[-0.02em] sm:text-3xl">
|
|
125
|
-
{newsletter.headline}
|
|
126
|
-
</h2>
|
|
127
|
-
<p className="mt-4 max-w-xl text-[15px] leading-relaxed text-zinc-500">{newsletter.subcopy}</p>
|
|
128
|
-
<div className="mt-7">
|
|
129
|
-
<NewsletterSignup newsletter={newsletter} />
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
</section>
|
|
133
|
-
|
|
134
|
-
{/* LINKS */}
|
|
135
|
-
{links && links.items.length > 0 ? (
|
|
120
|
+
{/* WORK WITH ME */}
|
|
121
|
+
{links ? (
|
|
136
122
|
<>
|
|
137
123
|
<Divider />
|
|
138
|
-
<section className={`${WRAP} py-
|
|
139
|
-
<
|
|
140
|
-
<
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
{l.
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
))}
|
|
154
|
-
</div>
|
|
124
|
+
<section id="work" className={`${WRAP} py-12 scroll-mt-20`}>
|
|
125
|
+
<SectionTitle>{links.headline}</SectionTitle>
|
|
126
|
+
<p className={`mt-5 ${PROSE}`}>{links.body}</p>
|
|
127
|
+
{links.items.length > 0 ? (
|
|
128
|
+
<ul className={`mt-5 space-y-2 ${PROSE}`}>
|
|
129
|
+
{links.items.map((l) => (
|
|
130
|
+
<li key={l.label}>
|
|
131
|
+
<a href={l.href} className="font-medium text-ink underline decoration-brand/50 underline-offset-4 hover:decoration-brand">
|
|
132
|
+
{l.label}
|
|
133
|
+
</a>
|
|
134
|
+
{l.note ? <span className="text-ink-2"> · {l.note}</span> : null}
|
|
135
|
+
</li>
|
|
136
|
+
))}
|
|
137
|
+
</ul>
|
|
138
|
+
) : null}
|
|
155
139
|
</section>
|
|
156
140
|
</>
|
|
157
141
|
) : null}
|
|
158
|
-
|
|
142
|
+
</>
|
|
159
143
|
);
|
|
160
144
|
}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
--brand: #4f46e5;
|
|
20
20
|
--brand-soft: #eef2ff;
|
|
21
21
|
--paper: #fafafa;
|
|
22
|
+
/* The marketing pages: warm paper, dark warm-grey type. Set from lib/site.config.ts. */
|
|
23
|
+
--ink: #2a2622;
|
|
24
|
+
--ink-2: #5b554e;
|
|
25
|
+
--rule: #d9d2c5;
|
|
22
26
|
--background: oklch(1 0 0);
|
|
23
27
|
--foreground: oklch(0.141 0.005 285.823);
|
|
24
28
|
--card: oklch(1 0 0);
|
|
@@ -112,6 +116,9 @@
|
|
|
112
116
|
--color-brand: var(--brand);
|
|
113
117
|
--color-brand-soft: var(--brand-soft);
|
|
114
118
|
--color-paper: var(--paper);
|
|
119
|
+
--color-ink: var(--ink);
|
|
120
|
+
--color-ink-2: var(--ink-2);
|
|
121
|
+
--color-rule: var(--rule);
|
|
115
122
|
--color-chart-1: var(--chart-1);
|
|
116
123
|
--color-chart-2: var(--chart-2);
|
|
117
124
|
--color-chart-3: var(--chart-3);
|
|
@@ -153,3 +160,10 @@
|
|
|
153
160
|
cursor: pointer;
|
|
154
161
|
}
|
|
155
162
|
}
|
|
163
|
+
|
|
164
|
+
/* The marketing pages. Source Serif 4 is declared in app.ts; /login and
|
|
165
|
+
/dashboard keep Inter. */
|
|
166
|
+
.font-display {
|
|
167
|
+
font-family: var(--font-display, "Iowan Old Style", "Palatino Linotype", Georgia, serif);
|
|
168
|
+
font-optical-sizing: auto;
|
|
169
|
+
}
|
package/templates/creator/app.ts
CHANGED
|
@@ -143,6 +143,16 @@ const manifest = buildManifest({
|
|
|
143
143
|
display: "swap",
|
|
144
144
|
preload: true,
|
|
145
145
|
}),
|
|
146
|
+
// The marketing pages are set in a serif, headings and running text alike.
|
|
147
|
+
font({
|
|
148
|
+
family: "Source Serif 4",
|
|
149
|
+
variable: "--font-display",
|
|
150
|
+
weights: ["400", "500", "600"],
|
|
151
|
+
styles: ["normal", "italic"],
|
|
152
|
+
subsets: ["latin"],
|
|
153
|
+
display: "swap",
|
|
154
|
+
preload: true,
|
|
155
|
+
}),
|
|
146
156
|
],
|
|
147
157
|
routes: await discoverAppRoutes(),
|
|
148
158
|
});
|
|
@@ -1,101 +1,30 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// app/layout.tsx, which read lib/site.config.ts — so re-theming is one edit.
|
|
3
|
+
// Presentational pieces for the marketing pages. All server-rendered, no client
|
|
4
|
+
// JS. Colors come from CSS vars set on <html> in app/layout.tsx, which read
|
|
5
|
+
// lib/site.config.ts, so re-theming is one edit there.
|
|
7
6
|
|
|
8
|
-
//
|
|
9
|
-
export const WRAP = "mx-auto w-full max-w-
|
|
10
|
-
|
|
11
|
-
export function Eyebrow({ children }: { children: React.ReactNode }) {
|
|
12
|
-
return (
|
|
13
|
-
<p className="font-mono text-[11px] font-semibold uppercase tracking-[0.14em] text-brand">
|
|
14
|
-
{children}
|
|
15
|
-
</p>
|
|
16
|
-
);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// "New / Coming soon"-style pill for the hero.
|
|
20
|
-
export function Badge({ children }: { children: React.ReactNode }) {
|
|
21
|
-
return (
|
|
22
|
-
<span className="inline-flex items-center gap-2 rounded-full border border-zinc-200 bg-white py-1 pl-1.5 pr-3 text-[13px] text-zinc-600 shadow-sm">
|
|
23
|
-
<span className="inline-block size-1.5 rounded-full bg-brand" />
|
|
24
|
-
{children}
|
|
25
|
-
</span>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
7
|
+
// One narrow column, left aligned, like a well-set essay page.
|
|
8
|
+
export const WRAP = "mx-auto w-full max-w-[40rem] px-6";
|
|
28
9
|
|
|
10
|
+
// A rule between sections.
|
|
29
11
|
export function Divider() {
|
|
30
12
|
return (
|
|
31
13
|
<div className={WRAP}>
|
|
32
|
-
<
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function SectionHead({
|
|
38
|
-
eyebrow,
|
|
39
|
-
title,
|
|
40
|
-
body,
|
|
41
|
-
}: {
|
|
42
|
-
eyebrow: string;
|
|
43
|
-
title: string;
|
|
44
|
-
body?: string;
|
|
45
|
-
}) {
|
|
46
|
-
return (
|
|
47
|
-
<div>
|
|
48
|
-
<Eyebrow>{eyebrow}</Eyebrow>
|
|
49
|
-
<h2 className="mt-4 text-balance text-2xl font-semibold leading-[1.15] tracking-[-0.02em] sm:text-3xl">
|
|
50
|
-
{title}
|
|
51
|
-
</h2>
|
|
52
|
-
{body ? (
|
|
53
|
-
<p className="mt-4 max-w-xl text-[15px] leading-relaxed text-zinc-500">
|
|
54
|
-
{body}
|
|
55
|
-
</p>
|
|
56
|
-
) : null}
|
|
14
|
+
<hr className="border-0 border-t border-rule" />
|
|
57
15
|
</div>
|
|
58
16
|
);
|
|
59
17
|
}
|
|
60
18
|
|
|
61
|
-
//
|
|
62
|
-
export function
|
|
63
|
-
|
|
64
|
-
}: {
|
|
65
|
-
items: { title: string; body: string; icon?: string }[];
|
|
66
|
-
}) {
|
|
67
|
-
return (
|
|
68
|
-
<div className="grid gap-6 sm:grid-cols-3">
|
|
69
|
-
{items.map((f) => (
|
|
70
|
-
<div key={f.title}>
|
|
71
|
-
{f.icon ? (
|
|
72
|
-
<span className="flex size-9 items-center justify-center rounded-lg bg-brand-soft text-brand">
|
|
73
|
-
{f.icon}
|
|
74
|
-
</span>
|
|
75
|
-
) : null}
|
|
76
|
-
<h3 className="mt-4 text-[15px] font-semibold text-zinc-900">
|
|
77
|
-
{f.title}
|
|
78
|
-
</h3>
|
|
79
|
-
<p className="mt-2 text-[14px] leading-relaxed text-zinc-500">
|
|
80
|
-
{f.body}
|
|
81
|
-
</p>
|
|
82
|
-
</div>
|
|
83
|
-
))}
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
19
|
+
// Section heading. One size, one weight, no label above it.
|
|
20
|
+
export function SectionTitle({ children }: { children: React.ReactNode }) {
|
|
21
|
+
return <h2 className="text-[1.5rem] font-medium leading-[1.2] text-ink">{children}</h2>;
|
|
86
22
|
}
|
|
87
23
|
|
|
88
|
-
//
|
|
89
|
-
export
|
|
90
|
-
return name
|
|
91
|
-
.split(/\s+/)
|
|
92
|
-
.map((w) => w[0])
|
|
93
|
-
.join("")
|
|
94
|
-
.slice(0, 2)
|
|
95
|
-
.toUpperCase();
|
|
96
|
-
}
|
|
24
|
+
// Running text: 17.5px with generous leading, the same serif as the headings.
|
|
25
|
+
export const PROSE = "text-[17.5px] leading-[1.65] text-ink-2";
|
|
97
26
|
|
|
98
|
-
// A deliberately-obvious image placeholder
|
|
27
|
+
// A deliberately-obvious image placeholder: dashed border, photo glyph, and a
|
|
99
28
|
// "swap this" hint. Real sites drop a photo here.
|
|
100
29
|
//
|
|
101
30
|
// shape — "landscape" | "portrait" | "square" | "circle"
|
|
@@ -105,11 +34,14 @@ export function ImagePlaceholder({
|
|
|
105
34
|
shape = "landscape",
|
|
106
35
|
title,
|
|
107
36
|
hint,
|
|
37
|
+
src,
|
|
108
38
|
className = "",
|
|
109
39
|
}: {
|
|
110
40
|
shape?: "landscape" | "portrait" | "square" | "circle";
|
|
111
41
|
title: string;
|
|
112
42
|
hint?: string;
|
|
43
|
+
/** A real image. The template ships one under public/images; swap it for yours. */
|
|
44
|
+
src?: string;
|
|
113
45
|
className?: string;
|
|
114
46
|
}) {
|
|
115
47
|
const aspect =
|
|
@@ -118,14 +50,21 @@ export function ImagePlaceholder({
|
|
|
118
50
|
: shape === "square" || shape === "circle"
|
|
119
51
|
? "aspect-square"
|
|
120
52
|
: "aspect-[4/3]";
|
|
121
|
-
const radius = shape === "circle" ? "rounded-full" : "rounded-
|
|
53
|
+
const radius = shape === "circle" ? "rounded-full" : "rounded-lg";
|
|
54
|
+
if (src) {
|
|
55
|
+
return (
|
|
56
|
+
<div className={`relative overflow-hidden bg-rule ${aspect} ${radius} ${className}`}>
|
|
57
|
+
<img src={src} alt={title} className="absolute inset-0 size-full object-cover" />
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
122
61
|
return (
|
|
123
62
|
<div
|
|
124
|
-
className={`relative grid place-items-center overflow-hidden border-2 border-dashed border-
|
|
63
|
+
className={`relative grid place-items-center overflow-hidden border-2 border-dashed border-rule bg-paper ${aspect} ${radius} ${className}`}
|
|
125
64
|
>
|
|
126
65
|
<div className="px-4 text-center">
|
|
127
66
|
<svg
|
|
128
|
-
className="mx-auto size-7 text-
|
|
67
|
+
className="mx-auto size-7 text-rule"
|
|
129
68
|
viewBox="0 0 24 24"
|
|
130
69
|
fill="none"
|
|
131
70
|
stroke="currentColor"
|
|
@@ -138,8 +77,8 @@ export function ImagePlaceholder({
|
|
|
138
77
|
<circle cx="9" cy="9" r="1.6" />
|
|
139
78
|
<path d="m21 15-4.5-4.5L7 20" />
|
|
140
79
|
</svg>
|
|
141
|
-
<p className="mt-2 text-[12.5px] font-medium text-
|
|
142
|
-
{hint ? <p className="mt-1 text-[11px] leading-snug text-
|
|
80
|
+
<p className="mt-2 text-[12.5px] font-medium text-ink-2">{title}</p>
|
|
81
|
+
{hint ? <p className="mt-1 text-[11px] leading-snug text-ink-2/70">{hint}</p> : null}
|
|
143
82
|
</div>
|
|
144
83
|
</div>
|
|
145
84
|
);
|