create-lacspace-app 1.17.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BENCHMARKS.md +3 -2
- package/README.md +16 -14
- package/dist/index.js +612 -348
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,15 +17,15 @@ var C = {
|
|
|
17
17
|
};
|
|
18
18
|
var c = (k, s) => `${C[k]}${s}${C.reset}`;
|
|
19
19
|
var TEMPLATES = [
|
|
20
|
-
{ key: "personal", label: "Personal portfolio", description: "A sleek personal / developer portfolio with projects and contact.", accent: ["#6366f1", "#a855f7"], siteName: "
|
|
21
|
-
{ key: "business", label: "Business site", description: "A professional company / agency site with services and a CTA.", accent: ["#2563eb", "#06b6d4"], siteName: "
|
|
22
|
-
{ key: "ecommerce", label: "E-commerce storefront", description: "A modern product storefront home with a featured grid.", accent: ["#0d9488", "#84cc16"], siteName: "
|
|
23
|
-
{ key: "saas", label: "SaaS landing", description: "A high-converting SaaS landing page with features and pricing.", accent: ["#7c3aed", "#ec4899"], siteName: "
|
|
24
|
-
{ key: "blog", label: "Blog / magazine", description: "A clean editorial blog home with a featured post and a grid.", accent: ["#f97316", "#ef4444"], siteName: "
|
|
25
|
-
{ key: "docs", label: "Documentation", description: "A docs landing with quick-start and feature cards.", accent: ["#0ea5e9", "#6366f1"], siteName: "
|
|
26
|
-
{ key: "dashboard", label: "Admin dashboard", description: "An app dashboard shell with stat cards and a table.", accent: ["#10b981", "#14b8a6"], siteName: "
|
|
27
|
-
{ key: "restaurant", label: "Restaurant / cafe", description: "A warm restaurant home with menu highlights and reservations.", accent: ["#e11d48", "#f59e0b"], siteName: "
|
|
28
|
-
{ key: "marketplace", label: "Marketplace / commerce", description: "A real storefront wired to the Lacspace commerce packages \u2014 cart, checkout, tax, shipping, orders, invoices and Nepal payments.", accent: ["#0d9488", "#6366f1"], siteName: "
|
|
20
|
+
{ key: "personal", label: "Personal portfolio", description: "A sleek personal / developer portfolio with projects and contact.", accent: ["#6366f1", "#a855f7"], siteName: "LSFolio", siteDescription: "Developer, designer & maker. Selected work and writing." },
|
|
21
|
+
{ key: "business", label: "Business site", description: "A professional company / agency site with services and a CTA.", accent: ["#2563eb", "#06b6d4"], siteName: "LSStudio", siteDescription: "We design and build digital products that grow businesses." },
|
|
22
|
+
{ key: "ecommerce", label: "E-commerce storefront", description: "A modern product storefront home with a featured grid.", accent: ["#0d9488", "#84cc16"], siteName: "LSStore", siteDescription: "Beautiful things, thoughtfully made. Free shipping worldwide." },
|
|
23
|
+
{ key: "saas", label: "SaaS landing", description: "A high-converting SaaS landing page with features and pricing.", accent: ["#7c3aed", "#ec4899"], siteName: "LSCloud", siteDescription: "The all-in-one platform your team will love. Ship faster." },
|
|
24
|
+
{ key: "blog", label: "Blog / magazine", description: "A clean editorial blog home with a featured post and a grid.", accent: ["#f97316", "#ef4444"], siteName: "LSBlogs", siteDescription: "Essays, notes and stories on building things that matter." },
|
|
25
|
+
{ key: "docs", label: "Documentation", description: "A docs landing with quick-start and feature cards.", accent: ["#0ea5e9", "#6366f1"], siteName: "LSDocs", siteDescription: "Everything you need to build with LSDocs \u2014 guides, API and examples." },
|
|
26
|
+
{ key: "dashboard", label: "Admin dashboard", description: "An app dashboard shell with stat cards and a table.", accent: ["#10b981", "#14b8a6"], siteName: "LSAdmin", siteDescription: "Your control center \u2014 metrics, activity and management in one place." },
|
|
27
|
+
{ key: "restaurant", label: "Restaurant / cafe", description: "A warm restaurant home with menu highlights and reservations.", accent: ["#e11d48", "#f59e0b"], siteName: "LSResto", siteDescription: "Seasonal plates, natural wine and a warm room. Book a table." },
|
|
28
|
+
{ key: "marketplace", label: "Marketplace / commerce", description: "A real storefront wired to the Lacspace commerce packages \u2014 cart, checkout, tax, shipping, orders, invoices and Nepal payments.", accent: ["#0d9488", "#6366f1"], siteName: "LSBazaar", siteDescription: "A modern storefront \u2014 cart to checkout, wired end to end." }
|
|
29
29
|
];
|
|
30
30
|
var pkgJson = (ctx) => JSON.stringify({
|
|
31
31
|
name: ctx.name,
|
|
@@ -105,6 +105,9 @@ var nextConfig = () => `import { toNextHeaders } from "@lacspace/headers";
|
|
|
105
105
|
|
|
106
106
|
/** @type {import('next').NextConfig} */
|
|
107
107
|
const nextConfig = {
|
|
108
|
+
// Serve under a sub-path (e.g. a templates.lacspace.com/<name> demo) by setting
|
|
109
|
+
// NEXT_PUBLIC_BASE_PATH at build time; leave it unset for a normal standalone app.
|
|
110
|
+
basePath: process.env.NEXT_PUBLIC_BASE_PATH || "",
|
|
108
111
|
async headers() {
|
|
109
112
|
// Hardened security headers (HSTS, CSP, X-Frame-Options, \u2026) from @lacspace/headers
|
|
110
113
|
return toNextHeaders();
|
|
@@ -139,30 +142,47 @@ var globalsCss = (ctx) => `@import "tailwindcss";
|
|
|
139
142
|
--color-surface: var(--surface);
|
|
140
143
|
--color-panel: var(--panel);
|
|
141
144
|
--color-hairline: var(--hairline);
|
|
145
|
+
--color-accent: var(--accent-to);
|
|
146
|
+
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
|
147
|
+
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
142
148
|
}
|
|
143
149
|
|
|
144
150
|
:root {
|
|
145
151
|
--accent-from: ${ctx.template.accent[0]};
|
|
146
152
|
--accent-to: ${ctx.template.accent[1]};
|
|
147
|
-
/*
|
|
148
|
-
--
|
|
153
|
+
/* on-accent text \u2014 accent gradients are saturated, so black reads cleanest */
|
|
154
|
+
--on-accent: #0a0a0f;
|
|
155
|
+
/* Light theme \u2014 warm off-white ground, near-black ink */
|
|
156
|
+
--bg: #fbfbfd;
|
|
149
157
|
--fg: #0a0a0f;
|
|
150
|
-
--muted: #
|
|
151
|
-
--faint: #
|
|
152
|
-
--surface: rgb(0 0 0 / 0.
|
|
153
|
-
--panel: rgb(0 0 0 / 0.
|
|
154
|
-
--hairline: rgb(0 0 0 / 0.
|
|
158
|
+
--muted: #52525b;
|
|
159
|
+
--faint: #8a8a94;
|
|
160
|
+
--surface: rgb(0 0 0 / 0.025);
|
|
161
|
+
--panel: rgb(0 0 0 / 0.05);
|
|
162
|
+
--hairline: rgb(0 0 0 / 0.08);
|
|
163
|
+
/* soft, layered shadows (never harsh) */
|
|
164
|
+
--shadow-sm: 0 1px 2px rgb(15 15 25 / 0.05), 0 1px 1px rgb(15 15 25 / 0.03);
|
|
165
|
+
--shadow: 0 4px 12px -2px rgb(15 15 25 / 0.08), 0 2px 6px -2px rgb(15 15 25 / 0.05);
|
|
166
|
+
--shadow-lg: 0 24px 48px -12px rgb(15 15 25 / 0.14), 0 8px 20px -8px rgb(15 15 25 / 0.08);
|
|
167
|
+
--glow: 0 0 0 1px rgb(0 0 0 / 0.04);
|
|
168
|
+
--radius: 1rem;
|
|
169
|
+
--radius-lg: 1.5rem;
|
|
155
170
|
}
|
|
156
171
|
|
|
157
172
|
.dark {
|
|
158
|
-
/* Dark theme (default) */
|
|
159
|
-
--
|
|
173
|
+
/* Dark theme (default) \u2014 deep near-black with a hint of indigo */
|
|
174
|
+
--on-accent: #0a0a0f;
|
|
175
|
+
--bg: #08080c;
|
|
160
176
|
--fg: #f5f5f7;
|
|
161
|
-
--muted: rgb(
|
|
162
|
-
--faint: rgb(
|
|
163
|
-
--surface: rgb(255 255 255 / 0.
|
|
164
|
-
--panel: rgb(255 255 255 / 0.
|
|
165
|
-
--hairline: rgb(255 255 255 / 0.
|
|
177
|
+
--muted: rgb(235 235 245 / 0.66);
|
|
178
|
+
--faint: rgb(235 235 245 / 0.42);
|
|
179
|
+
--surface: rgb(255 255 255 / 0.035);
|
|
180
|
+
--panel: rgb(255 255 255 / 0.07);
|
|
181
|
+
--hairline: rgb(255 255 255 / 0.09);
|
|
182
|
+
--shadow-sm: 0 1px 2px rgb(0 0 0 / 0.4);
|
|
183
|
+
--shadow: 0 8px 24px -6px rgb(0 0 0 / 0.5);
|
|
184
|
+
--shadow-lg: 0 32px 64px -16px rgb(0 0 0 / 0.65), 0 12px 28px -12px rgb(0 0 0 / 0.5);
|
|
185
|
+
--glow: 0 0 0 1px rgb(255 255 255 / 0.06);
|
|
166
186
|
}
|
|
167
187
|
|
|
168
188
|
* { border-color: var(--hairline); }
|
|
@@ -172,49 +192,129 @@ body {
|
|
|
172
192
|
color: var(--fg);
|
|
173
193
|
-webkit-font-smoothing: antialiased;
|
|
174
194
|
text-rendering: optimizeLegibility;
|
|
195
|
+
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
|
|
175
196
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
176
197
|
}
|
|
177
198
|
|
|
199
|
+
/* Confident, tight display type \u2014 a fluid clamp() scale + negative tracking. */
|
|
200
|
+
h1, h2, h3, .display { letter-spacing: -0.02em; text-wrap: balance; }
|
|
201
|
+
h1, .display { letter-spacing: -0.035em; line-height: 1.02; }
|
|
202
|
+
.text-display { font-size: clamp(2.6rem, 6.5vw, 4.75rem); line-height: 1; letter-spacing: -0.04em; font-weight: 800; text-wrap: balance; }
|
|
203
|
+
.text-hero { font-size: clamp(2.2rem, 5.5vw, 3.75rem); line-height: 1.03; letter-spacing: -0.035em; font-weight: 800; text-wrap: balance; }
|
|
204
|
+
.lead { font-size: clamp(1.05rem, 1.6vw, 1.25rem); line-height: 1.6; color: var(--muted); text-wrap: pretty; }
|
|
205
|
+
|
|
178
206
|
::selection { background: var(--accent-to); color: #fff; }
|
|
179
207
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
180
|
-
::-webkit-scrollbar-thumb { background: var(--panel); border-radius: 8px; }
|
|
181
|
-
:
|
|
208
|
+
::-webkit-scrollbar-thumb { background: var(--panel); border-radius: 8px; border: 2px solid transparent; background-clip: padding-box; }
|
|
209
|
+
::-webkit-scrollbar-thumb:hover { background: var(--hairline); }
|
|
210
|
+
:focus-visible { outline: 2px solid var(--accent-to); outline-offset: 2px; border-radius: 6px; }
|
|
182
211
|
|
|
183
212
|
.gradient-text {
|
|
184
|
-
background: linear-gradient(
|
|
213
|
+
background: linear-gradient(115deg, var(--accent-from), var(--accent-to));
|
|
185
214
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
186
215
|
}
|
|
187
|
-
.gradient-bg { background: linear-gradient(
|
|
216
|
+
.gradient-bg { background: linear-gradient(115deg, var(--accent-from), var(--accent-to)); }
|
|
217
|
+
.on-accent { color: var(--on-accent); }
|
|
218
|
+
|
|
219
|
+
/* soft accent halo used behind hero type & badges */
|
|
220
|
+
.accent-glow { box-shadow: 0 8px 32px -8px color-mix(in oklab, var(--accent-to) 55%, transparent); }
|
|
188
221
|
|
|
189
222
|
/* opaque theme background (for sticky chrome & chips) */
|
|
190
223
|
.bg-app { background: var(--bg); }
|
|
191
224
|
|
|
225
|
+
/* frosted glass \u2014 translucent surface + blur + hairline */
|
|
226
|
+
.glass {
|
|
227
|
+
background: color-mix(in oklab, var(--bg) 72%, transparent);
|
|
228
|
+
backdrop-filter: blur(14px) saturate(1.4);
|
|
229
|
+
-webkit-backdrop-filter: blur(14px) saturate(1.4);
|
|
230
|
+
border: 1px solid var(--hairline);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* the workhorse surface card \u2014 rounded, hairline, soft shadow, hover lift + glow */
|
|
234
|
+
.card {
|
|
235
|
+
border-radius: var(--radius-lg);
|
|
236
|
+
border: 1px solid var(--hairline);
|
|
237
|
+
background: var(--surface);
|
|
238
|
+
box-shadow: var(--shadow-sm);
|
|
239
|
+
transition: transform 0.4s var(--ease-out-expo), box-shadow 0.4s var(--ease-out-expo), border-color 0.4s var(--ease-out-expo);
|
|
240
|
+
}
|
|
241
|
+
.card:hover {
|
|
242
|
+
transform: translateY(-4px);
|
|
243
|
+
box-shadow: var(--shadow-lg);
|
|
244
|
+
border-color: color-mix(in oklab, var(--accent-to) 40%, var(--hairline));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/* subtle gradient hairline border (draws focus without shouting) */
|
|
248
|
+
.gradient-border { position: relative; }
|
|
249
|
+
.gradient-border::after {
|
|
250
|
+
content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
|
|
251
|
+
background: linear-gradient(140deg, color-mix(in oklab, var(--accent-from) 60%, transparent), transparent 45%);
|
|
252
|
+
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
253
|
+
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
254
|
+
-webkit-mask-composite: xor; mask-composite: exclude; pointer-events: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* subtle background patterns for section grounds */
|
|
258
|
+
.grid-bg {
|
|
259
|
+
background-image: linear-gradient(var(--hairline) 1px, transparent 1px), linear-gradient(90deg, var(--hairline) 1px, transparent 1px);
|
|
260
|
+
background-size: 56px 56px;
|
|
261
|
+
-webkit-mask-image: radial-gradient(ellipse 70% 60% at 50% 0%, #000 30%, transparent 75%);
|
|
262
|
+
mask-image: radial-gradient(ellipse 70% 60% at 50% 0%, #000 30%, transparent 75%);
|
|
263
|
+
}
|
|
264
|
+
.dot-bg {
|
|
265
|
+
background-image: radial-gradient(var(--hairline) 1px, transparent 1px);
|
|
266
|
+
background-size: 22px 22px;
|
|
267
|
+
-webkit-mask-image: radial-gradient(ellipse 60% 50% at 50% 40%, #000 20%, transparent 70%);
|
|
268
|
+
mask-image: radial-gradient(ellipse 60% 50% at 50% 40%, #000 20%, transparent 70%);
|
|
269
|
+
}
|
|
270
|
+
|
|
192
271
|
/* indeterminate progress bar (used by the "under development" pages) */
|
|
193
272
|
@keyframes loadbar { 0% { transform: translateX(-120%); } 100% { transform: translateX(340%); } }
|
|
194
273
|
|
|
195
274
|
/* ambient motion for illustrations & the aurora background */
|
|
196
|
-
@keyframes floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-
|
|
275
|
+
@keyframes floaty { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-18px); } }
|
|
276
|
+
@keyframes drift { 0%, 100% { transform: translate3d(0, 0, 0) scale(1); } 33% { transform: translate3d(4%, -3%, 0) scale(1.06); } 66% { transform: translate3d(-3%, 3%, 0) scale(0.96); } }
|
|
197
277
|
@keyframes spin-slow { to { transform: rotate(360deg); } }
|
|
198
278
|
@keyframes gradient-pan { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } }
|
|
199
279
|
@keyframes marquee-x { from { transform: translateX(0); } to { transform: translateX(-50%); } }
|
|
280
|
+
@keyframes shimmer-sweep { 0% { transform: translateX(-120%) skewX(-16deg); } 60%, 100% { transform: translateX(320%) skewX(-16deg); } }
|
|
200
281
|
.animate-floaty { animation: floaty 6s ease-in-out infinite; }
|
|
201
282
|
.animate-floaty-slow { animation: floaty 9s ease-in-out infinite; }
|
|
202
|
-
.animate-
|
|
283
|
+
.animate-drift { animation: drift 22s ease-in-out infinite; }
|
|
284
|
+
.animate-drift-slow { animation: drift 30s ease-in-out infinite; }
|
|
285
|
+
.animate-spin-slow { animation: spin-slow 30s linear infinite; }
|
|
203
286
|
.gradient-text-animated {
|
|
204
|
-
background: linear-gradient(
|
|
287
|
+
background: linear-gradient(115deg, var(--accent-from), var(--accent-to), var(--accent-from));
|
|
205
288
|
background-size: 200% 200%;
|
|
206
289
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
207
290
|
animation: gradient-pan 6s ease infinite;
|
|
208
291
|
}
|
|
209
|
-
.marquee-track { display: flex; width: max-content; animation: marquee-x
|
|
292
|
+
.marquee-track { display: flex; width: max-content; animation: marquee-x 40s linear infinite; }
|
|
293
|
+
.marquee-track:hover { animation-play-state: paused; }
|
|
210
294
|
@keyframes draw { to { stroke-dashoffset: 0; } }
|
|
211
|
-
.animate-draw { stroke-dasharray: 2000; stroke-dashoffset: 2000; animation: draw 1.8s ease forwards; }
|
|
295
|
+
.animate-draw { stroke-dasharray: 2000; stroke-dashoffset: 2000; animation: draw 1.8s var(--ease-out-expo) forwards; }
|
|
212
296
|
.marquee-mask { -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
|
|
213
297
|
|
|
214
|
-
/*
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
298
|
+
/* a slow gradient light-sweep for primary CTAs */
|
|
299
|
+
.shimmer { position: relative; overflow: hidden; }
|
|
300
|
+
.shimmer::after {
|
|
301
|
+
content: ""; position: absolute; top: 0; bottom: 0; left: 0; width: 40%;
|
|
302
|
+
background: linear-gradient(90deg, transparent, rgb(255 255 255 / 0.45), transparent);
|
|
303
|
+
animation: shimmer-sweep 4.5s var(--ease-out-expo) infinite; pointer-events: none;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* soft, staggered entrance for content \u2014 nicer easing */
|
|
307
|
+
@keyframes rise { from { opacity: 0; transform: translateY(16px); } to { opacity: 1; transform: none; } }
|
|
308
|
+
main > section { animation: rise 0.7s var(--ease-out-expo) both; }
|
|
309
|
+
main > section:nth-of-type(2) { animation-delay: 0.06s; }
|
|
310
|
+
main > section:nth-of-type(3) { animation-delay: 0.12s; }
|
|
311
|
+
main > section:nth-of-type(4) { animation-delay: 0.16s; }
|
|
312
|
+
|
|
313
|
+
/* respect reduced motion \u2014 kill animation AND transition-driven transforms */
|
|
314
|
+
@media (prefers-reduced-motion: reduce) {
|
|
315
|
+
*, ::before, ::after { animation: none !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
|
|
316
|
+
.shimmer::after { display: none; }
|
|
317
|
+
}
|
|
218
318
|
${ctx.template.key === "blog" || ctx.template.key === "docs" ? BLOG_PROSE_CSS : ""}`;
|
|
219
319
|
var BLOG_PROSE_CSS = `
|
|
220
320
|
/* article typography for Markdown posts */
|
|
@@ -265,13 +365,13 @@ import { site } from "@/lib/site";
|
|
|
265
365
|
import { CommandMenu } from "@/components/command-menu";
|
|
266
366
|
${imports}import "./globals.css";
|
|
267
367
|
|
|
268
|
-
const inter = Inter({ subsets: ["latin"], display: "swap" });
|
|
368
|
+
const inter = Inter({ subsets: ["latin"], display: "swap", variable: "--font-inter" });
|
|
269
369
|
|
|
270
370
|
export const metadata: Metadata = site.meta({ title: ${JSON.stringify(ctx.template.siteName)} });
|
|
271
371
|
|
|
272
372
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
273
373
|
return (
|
|
274
|
-
<html lang="en" className={inter.className} suppressHydrationWarning>
|
|
374
|
+
<html lang="en" className={\`\${inter.variable} \${inter.className}\`} suppressHydrationWarning>
|
|
275
375
|
<body className="antialiased">
|
|
276
376
|
{/* \u2728 Dark / light / system theming with a built-in no-flash script \u2014 @lacspace/theme */}
|
|
277
377
|
<ThemeProvider defaultTheme="dark">
|
|
@@ -321,10 +421,11 @@ var notFound = () => `import Link from "next/link";
|
|
|
321
421
|
|
|
322
422
|
export default function NotFound() {
|
|
323
423
|
return (
|
|
324
|
-
<main className="flex min-h-screen flex-col items-center justify-center gap-6 px-6 text-center">
|
|
325
|
-
<div className="
|
|
326
|
-
<
|
|
327
|
-
<
|
|
424
|
+
<main className="relative flex min-h-screen flex-col items-center justify-center gap-6 overflow-hidden px-6 text-center">
|
|
425
|
+
<div aria-hidden className="pointer-events-none absolute h-96 w-96 rounded-full gradient-bg opacity-20 blur-3xl animate-drift-slow" />
|
|
426
|
+
<div className="relative text-[9rem] font-black leading-none gradient-text-animated">404</div>
|
|
427
|
+
<p className="relative text-lg text-muted">This page wandered off. Let's get you home.</p>
|
|
428
|
+
<Link href="/" className="shimmer relative rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Back home</Link>
|
|
328
429
|
</main>
|
|
329
430
|
);
|
|
330
431
|
}
|
|
@@ -443,11 +544,22 @@ function homePage(ctx) {
|
|
|
443
544
|
const n = ctx.template.siteName;
|
|
444
545
|
if (ctx.template.key === "dashboard") return dashboardHome(ctx);
|
|
445
546
|
if (ctx.template.key === "marketplace") return marketplaceHome(ctx);
|
|
446
|
-
const shell = (inner) =>
|
|
547
|
+
const shell = (inner) => {
|
|
548
|
+
const ctaCopy = {
|
|
549
|
+
personal: { t: "Have something in mind?", s: "I'm taking on a couple of projects this quarter \u2014 let's talk.", l: "Start a conversation", h: "/contact" },
|
|
550
|
+
business: { t: "Let's build something that ships", s: "Tell us the outcome you're after and we'll map the path.", l: "Start a project", h: "/contact" },
|
|
551
|
+
ecommerce: { t: "Find your next favourite thing", s: "Free shipping over $50 and 30-day easy returns, always.", l: "Shop the collection", h: "/shop" },
|
|
552
|
+
saas: { t: "Ready to ship faster?", s: "Start free in minutes \u2014 no credit card, no lock-in.", l: "Get started free", h: "/pricing" },
|
|
553
|
+
blog: { t: "Never miss a piece", s: "Get new essays in your inbox \u2014 no spam, unsubscribe anytime.", l: "Join the newsletter", h: "/newsletter" },
|
|
554
|
+
docs: { t: "Start building today", s: "Go from install to your first request in five minutes.", l: "Read the quickstart", h: "/docs" },
|
|
555
|
+
restaurant: { t: "Join us for dinner", s: "We fill up fast on weekends \u2014 book your table ahead.", l: "Reserve a table", h: "/reservations" }
|
|
556
|
+
};
|
|
557
|
+
const cc = ctaCopy[ctx.template.key] ?? ctaCopy.business;
|
|
558
|
+
return `import { site } from "@/lib/site";
|
|
447
559
|
import { LiveStats } from "@/components/live-stats";
|
|
448
560
|
import { Aurora } from "@/components/aurora";
|
|
449
561
|
import { HeroArt } from "@/components/hero-art";
|
|
450
|
-
import { StatBand, Steps, Testimonial } from "@/components/ui";
|
|
562
|
+
import { StatBand, Steps, Testimonial, CTABand } from "@/components/ui";
|
|
451
563
|
|
|
452
564
|
// \u2728 Self-canonical home page \u2014 one line, full SEO (title, canonical, OG, Twitter).
|
|
453
565
|
export const metadata = site.meta({ title: ${JSON.stringify(n)}, path: "/" });
|
|
@@ -455,202 +567,229 @@ export const metadata = site.meta({ title: ${JSON.stringify(n)}, path: "/" });
|
|
|
455
567
|
export default function Home() {
|
|
456
568
|
return (
|
|
457
569
|
<main className="relative min-h-screen overflow-hidden">
|
|
458
|
-
{/* \u2728
|
|
570
|
+
{/* \u2728 Soft, theme-aware gradient-mesh backdrop */}
|
|
459
571
|
<Aurora />
|
|
460
572
|
${inner}
|
|
461
|
-
${showcaseSection(ctx)}
|
|
462
573
|
${marqueeSection(ctx)}
|
|
574
|
+
${showcaseSection(ctx)}
|
|
463
575
|
${extraHomeSections(ctx)}
|
|
576
|
+
<CTABand title=${JSON.stringify(cc.t)} subtitle=${JSON.stringify(cc.s)} ctaLabel=${JSON.stringify(cc.l)} ctaHref=${JSON.stringify(cc.h)} />
|
|
464
577
|
${faqSection(ctx)}
|
|
465
578
|
${builtWithSection(ctx)}
|
|
466
579
|
</main>
|
|
467
580
|
);
|
|
468
581
|
}
|
|
469
582
|
`;
|
|
583
|
+
};
|
|
470
584
|
if (ctx.template.key === "personal") {
|
|
471
|
-
return shell(`<section className="mx-auto max-w-3xl px-6 py-
|
|
472
|
-
<
|
|
473
|
-
<h1 className="text-
|
|
474
|
-
<p className="mx-auto mt-6 max-w-xl
|
|
475
|
-
<div className="mt-10 flex justify-center gap-
|
|
476
|
-
<a href="#work" className="
|
|
477
|
-
<a href="
|
|
585
|
+
return shell(`<section className="relative mx-auto max-w-3xl px-6 py-32 text-center sm:py-40">
|
|
586
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted"><span className="relative flex h-2 w-2"><span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" /><span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-400" /></span> Available for select projects</span>
|
|
587
|
+
<h1 className="text-display mt-7">Hi, I'm <span className="gradient-text">${n}</span></h1>
|
|
588
|
+
<p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
|
|
589
|
+
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
|
590
|
+
<a href="#work" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">View my work</a>
|
|
591
|
+
<a href="/contact" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">Get in touch</a>
|
|
478
592
|
</div>
|
|
479
593
|
</section>
|
|
480
|
-
<section id="work" className="mx-auto max-w-6xl px-6 py-
|
|
481
|
-
<
|
|
594
|
+
<section id="work" className="mx-auto max-w-6xl px-6 py-20">
|
|
595
|
+
<div className="mb-12 flex items-end justify-between">
|
|
596
|
+
<div><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Selected work</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Things I've shipped</h2></div>
|
|
597
|
+
<a href="/work" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">All projects \u2192</a>
|
|
598
|
+
</div>
|
|
482
599
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
483
|
-
{[
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
600
|
+
{[
|
|
601
|
+
{ t: "Aurora Design System", d: "A themeable component library and tokens powering 40+ product screens.", tag: "Design + Code", e: "\u{1F3A8}" },
|
|
602
|
+
{ t: "Northwind Analytics", d: "A real-time dashboard that cut reporting time from hours to seconds.", tag: "Product", e: "\u{1F4CA}" },
|
|
603
|
+
{ t: "Lumen Mobile", d: "A 4.9\u2605 React Native app rebuilt from the ground up for speed.", tag: "Mobile", e: "\u{1F4F1}" },
|
|
604
|
+
{ t: "Harbor Commerce", d: "A headless storefront with a checkout that converts 38% better.", tag: "E-commerce", e: "\u{1F6CD}\uFE0F" },
|
|
605
|
+
{ t: "Cadence Marketing", d: "A brand and site refresh that doubled qualified inbound leads.", tag: "Brand", e: "\u2728" },
|
|
606
|
+
{ t: "Meadow Docs", d: "A fast, searchable docs platform teams actually enjoy reading.", tag: "Web", e: "\u{1F4DA}" },
|
|
607
|
+
].map((p) => (
|
|
608
|
+
<a key={p.t} href="/work" className="card group flex flex-col p-6">
|
|
609
|
+
<div className="mb-5 flex aspect-[16/10] items-center justify-center overflow-hidden rounded-xl gradient-bg text-5xl on-accent">{p.e}</div>
|
|
610
|
+
<span className="text-xs font-semibold uppercase tracking-widest gradient-text">{p.tag}</span>
|
|
611
|
+
<h3 className="mt-1.5 font-semibold tracking-tight">{p.t}</h3>
|
|
612
|
+
<p className="mt-1.5 text-sm leading-relaxed text-muted">{p.d}</p>
|
|
613
|
+
</a>
|
|
489
614
|
))}
|
|
490
615
|
</div>
|
|
491
616
|
</section>`);
|
|
492
617
|
}
|
|
493
618
|
if (ctx.template.key === "business") {
|
|
494
|
-
return shell(`<section className="mx-auto max-w-4xl px-6 py-
|
|
495
|
-
<
|
|
496
|
-
<
|
|
497
|
-
<
|
|
619
|
+
return shell(`<section className="relative mx-auto max-w-4xl px-6 py-32 text-center sm:py-40">
|
|
620
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted"><span aria-hidden className="h-1.5 w-1.5 rounded-full gradient-bg" /> A senior product studio</span>
|
|
621
|
+
<h1 className="text-display mt-7">We build products <span className="gradient-text">that grow businesses</span></h1>
|
|
622
|
+
<p className="lead mx-auto mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
|
|
623
|
+
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
|
624
|
+
<a href="/contact" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start a project</a>
|
|
625
|
+
<a href="/work" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">See our work</a>
|
|
626
|
+
</div>
|
|
498
627
|
</section>
|
|
499
|
-
<section id="services" className="mx-auto max-w-6xl px-6 py-
|
|
500
|
-
<
|
|
628
|
+
<section id="services" className="mx-auto max-w-6xl px-6 py-20">
|
|
629
|
+
<div className="mb-12 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">What we do</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">One team, from strategy to launch</h2></div>
|
|
501
630
|
<div className="grid gap-6 md:grid-cols-3">
|
|
502
631
|
{[
|
|
503
|
-
{ t: "Strategy", d: "Positioning, research and a roadmap that ships." },
|
|
504
|
-
{ t: "Design", d: "Brand and product design that
|
|
505
|
-
{ t: "Engineering", d: "Fast, reliable software built to last." },
|
|
632
|
+
{ t: "Strategy", d: "Positioning, research and a roadmap that ships \u2014 grounded in evidence, not opinions.", e: "\u{1F9ED}" },
|
|
633
|
+
{ t: "Design", d: "Brand and product design people remember, with a system that scales past launch day.", e: "\u{1F3A8}" },
|
|
634
|
+
{ t: "Engineering", d: "Fast, reliable software built to last \u2014 typed, tested and observable from day one.", e: "\u2699\uFE0F" },
|
|
506
635
|
].map((s) => (
|
|
507
|
-
<div key={s.t} className="
|
|
508
|
-
<
|
|
509
|
-
<
|
|
636
|
+
<div key={s.t} className="card p-8">
|
|
637
|
+
<div className="mb-5 inline-flex h-12 w-12 items-center justify-center rounded-2xl gradient-bg text-2xl accent-glow">{s.e}</div>
|
|
638
|
+
<h3 className="text-xl font-bold tracking-tight">{s.t}</h3>
|
|
639
|
+
<p className="mt-2 leading-relaxed text-muted">{s.d}</p>
|
|
510
640
|
</div>
|
|
511
641
|
))}
|
|
512
642
|
</div>
|
|
513
|
-
</section>
|
|
514
|
-
<section id="contact" className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
515
|
-
<h2 className="text-3xl font-bold">Let's work together</h2>
|
|
516
|
-
<p className="mt-3 text-muted">Tell us about your project and we'll get back within a day.</p>
|
|
517
|
-
<a href="mailto:hello@example.com" className="mt-8 inline-block rounded-full border border-hairline px-8 py-3 font-semibold hover:bg-surface">hello@example.com</a>
|
|
518
643
|
</section>`);
|
|
519
644
|
}
|
|
520
645
|
if (ctx.template.key === "ecommerce") {
|
|
521
|
-
return shell(`<section className="mx-auto max-w-5xl px-6 py-
|
|
522
|
-
<
|
|
523
|
-
<
|
|
524
|
-
<
|
|
646
|
+
return shell(`<section className="relative mx-auto max-w-5xl px-6 py-32 text-center sm:py-40">
|
|
647
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted">\u{1F69A} Free worldwide shipping over $50</span>
|
|
648
|
+
<h1 className="text-display mt-7"><span className="gradient-text">${n}</span></h1>
|
|
649
|
+
<p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
|
|
650
|
+
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
|
651
|
+
<a href="/shop" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Shop the collection</a>
|
|
652
|
+
<a href="/collections" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">Browse collections</a>
|
|
653
|
+
</div>
|
|
525
654
|
</section>
|
|
526
|
-
<section id="shop" className="mx-auto max-w-6xl px-6 py-
|
|
527
|
-
<
|
|
655
|
+
<section id="shop" className="mx-auto max-w-6xl px-6 py-20">
|
|
656
|
+
<div className="mb-12 flex items-end justify-between">
|
|
657
|
+
<div><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Featured</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">This week's edit</h2></div>
|
|
658
|
+
<a href="/shop" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">Shop all \u2192</a>
|
|
659
|
+
</div>
|
|
528
660
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
529
661
|
{[
|
|
530
|
-
{ n: "Aurora Lamp", p: "$89" }, { n: "Terra Mug", p: "$24" },
|
|
531
|
-
{ n: "Linen Throw", p: "$65" }, { n: "Oak Stand", p: "$120" },
|
|
662
|
+
{ n: "Aurora Table Lamp", p: "$89", e: "\u{1F4A1}" }, { n: "Terra Stoneware Mug", p: "$24", e: "\u2615" },
|
|
663
|
+
{ n: "Linen Waffle Throw", p: "$65", e: "\u{1F9FA}" }, { n: "Oak Monitor Stand", p: "$120", e: "\u{1FAB5}" },
|
|
532
664
|
].map((prod) => (
|
|
533
|
-
<div key={prod.n} className="group
|
|
534
|
-
<
|
|
535
|
-
<div className="flex items-
|
|
536
|
-
<h3 className="font-semibold">{prod.n}</h3>
|
|
537
|
-
<span className="text-muted">{prod.p}</span>
|
|
665
|
+
<div key={prod.n} className="card group flex flex-col p-4">
|
|
666
|
+
<a href="/shop" className="mb-4 flex aspect-square items-center justify-center overflow-hidden rounded-xl gradient-bg text-6xl on-accent transition group-hover:scale-[1.03]">{prod.e}</a>
|
|
667
|
+
<div className="flex items-start justify-between gap-2">
|
|
668
|
+
<h3 className="font-semibold tracking-tight">{prod.n}</h3>
|
|
669
|
+
<span className="shrink-0 font-semibold text-muted">{prod.p}</span>
|
|
538
670
|
</div>
|
|
539
|
-
<a href="/shop" className="mt-
|
|
671
|
+
<a href="/shop" className="mt-4 block w-full rounded-full gradient-bg py-2.5 text-center text-sm font-semibold on-accent transition hover:-translate-y-0.5">Add to cart</a>
|
|
540
672
|
</div>
|
|
541
673
|
))}
|
|
542
674
|
</div>
|
|
543
675
|
</section>`);
|
|
544
676
|
}
|
|
545
677
|
if (ctx.template.key === "blog") {
|
|
546
|
-
return shell(`<section className="mx-auto max-w-3xl px-6 py-
|
|
547
|
-
<p className="
|
|
548
|
-
<h1 className="text-
|
|
549
|
-
<p className="mt-6
|
|
678
|
+
return shell(`<section className="mx-auto max-w-3xl px-6 py-28 sm:py-32">
|
|
679
|
+
<p className="text-sm font-semibold uppercase tracking-widest gradient-text">The Journal</p>
|
|
680
|
+
<h1 className="text-display mt-4"><span className="gradient-text">${n}</span></h1>
|
|
681
|
+
<p className="lead mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
|
|
682
|
+
<a href="/blog" className="mt-8 inline-flex items-center gap-2 text-sm font-semibold text-fg underline-offset-4 hover:underline">Read the archive \u2192</a>
|
|
550
683
|
</section>
|
|
551
684
|
<section id="latest" className="mx-auto max-w-5xl px-6 pb-8">
|
|
552
|
-
<a href="
|
|
553
|
-
<div className="
|
|
554
|
-
<
|
|
555
|
-
|
|
556
|
-
|
|
685
|
+
<a href="/blog" className="card group grid gap-6 overflow-hidden p-6 sm:grid-cols-[1.2fr_1fr] sm:p-8">
|
|
686
|
+
<div className="flex aspect-[16/10] items-center justify-center overflow-hidden rounded-2xl gradient-bg text-6xl on-accent transition group-hover:scale-[1.02]">\u{1F4EE}</div>
|
|
687
|
+
<div className="flex flex-col justify-center">
|
|
688
|
+
<span className="text-xs font-semibold uppercase tracking-widest gradient-text">Featured</span>
|
|
689
|
+
<h2 className="mt-3 text-2xl font-bold tracking-tight sm:text-3xl">The one thing every product needs before launch</h2>
|
|
690
|
+
<p className="mt-3 leading-relaxed text-muted">Most teams polish the wrong things. Here's the single unglamorous check that separates a launch that lands from one that limps.</p>
|
|
691
|
+
<span className="mt-5 text-sm text-faint">8 min read \xB7 Product</span>
|
|
692
|
+
</div>
|
|
557
693
|
</a>
|
|
558
694
|
</section>
|
|
559
|
-
<section className="mx-auto max-w-5xl px-6 py-
|
|
695
|
+
<section className="mx-auto max-w-5xl px-6 py-16">
|
|
560
696
|
<div className="grid gap-8 sm:grid-cols-2">
|
|
561
|
-
{[
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
697
|
+
{[
|
|
698
|
+
{ t: "Writing that survives the scroll", d: "Editing tricks that keep readers past the first paragraph.", tag: "Craft", e: "\u270D\uFE0F" },
|
|
699
|
+
{ t: "The cost of a fast 'yes'", d: "Why the cheapest decisions are often the most expensive.", tag: "Product", e: "\u2696\uFE0F" },
|
|
700
|
+
{ t: "Building in the open, one year in", d: "What we learned shipping every week where anyone could watch.", tag: "Culture", e: "\u{1F331}" },
|
|
701
|
+
{ t: "Design systems for teams of one", d: "You don't need a committee to move fast and stay consistent.", tag: "Design", e: "\u{1F3A8}" },
|
|
702
|
+
].map((p) => (
|
|
703
|
+
<a key={p.t} href="/blog" className="group">
|
|
704
|
+
<div className="mb-4 flex aspect-[16/9] items-center justify-center overflow-hidden rounded-2xl gradient-bg text-5xl on-accent transition group-hover:scale-[1.02]">{p.e}</div>
|
|
705
|
+
<span className="text-xs font-semibold uppercase tracking-widest gradient-text">{p.tag}</span>
|
|
706
|
+
<h3 className="mt-1.5 text-xl font-semibold tracking-tight transition group-hover:text-[color:var(--accent-to)]">{p.t}</h3>
|
|
707
|
+
<p className="mt-1.5 text-sm leading-relaxed text-muted">{p.d}</p>
|
|
567
708
|
</a>
|
|
568
709
|
))}
|
|
569
710
|
</div>
|
|
570
711
|
</section>`);
|
|
571
712
|
}
|
|
572
713
|
if (ctx.template.key === "docs") {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
<
|
|
576
|
-
<
|
|
577
|
-
|
|
714
|
+
const pkg = "@" + n.toLowerCase() + "/sdk";
|
|
715
|
+
return shell(`<section className="relative mx-auto max-w-4xl px-6 py-32 text-center sm:py-40">
|
|
716
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted">\u{1F4D8} Docs \xB7 v1.0</span>
|
|
717
|
+
<h1 className="text-display mt-7">Build with <span className="gradient-text">${n}</span></h1>
|
|
718
|
+
<p className="lead mx-auto mt-6 max-w-2xl">Guides, API references and copy-paste examples \u2014 everything you need, in one fast, searchable place.</p>
|
|
719
|
+
<div className="mt-9 flex flex-wrap items-center justify-center gap-3">
|
|
720
|
+
<a href="/docs" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Read the docs</a>
|
|
721
|
+
<div className="glass inline-flex items-center gap-3 rounded-full px-5 py-3 font-mono text-sm text-muted"><span className="gradient-text font-bold">$</span> npm i ${pkg}</div>
|
|
578
722
|
</div>
|
|
579
723
|
</section>
|
|
580
|
-
<section id="guides" className="mx-auto max-w-6xl px-6 py-
|
|
724
|
+
<section id="guides" className="mx-auto max-w-6xl px-6 py-16">
|
|
581
725
|
<div className="grid gap-6 md:grid-cols-3">
|
|
582
726
|
{[
|
|
583
|
-
{ t: "Quick start", d: "Go from zero to your first request in five minutes." },
|
|
584
|
-
{ t: "Guides", d: "Task-focused walkthroughs for the
|
|
585
|
-
{ t: "API reference", d: "Every endpoint, typed, with copy-paste examples." },
|
|
727
|
+
{ t: "Quick start", d: "Go from zero to your first successful request in under five minutes.", e: "\u{1F680}", href: "/docs" },
|
|
728
|
+
{ t: "Guides", d: "Task-focused walkthroughs for the paths you'll actually take.", e: "\u{1F9ED}", href: "/guides" },
|
|
729
|
+
{ t: "API reference", d: "Every endpoint, fully typed, with copy-paste examples in each language.", e: "\u{1F50C}", href: "/api-reference" },
|
|
586
730
|
].map((c) => (
|
|
587
|
-
<a key={c.t} href=
|
|
588
|
-
<
|
|
589
|
-
<
|
|
731
|
+
<a key={c.t} href={c.href} className="card group p-8">
|
|
732
|
+
<div className="mb-5 inline-flex h-12 w-12 items-center justify-center rounded-2xl gradient-bg text-2xl accent-glow">{c.e}</div>
|
|
733
|
+
<h3 className="text-xl font-bold tracking-tight">{c.t} <span className="inline-block transition group-hover:translate-x-1">\u2192</span></h3>
|
|
734
|
+
<p className="mt-2 leading-relaxed text-muted">{c.d}</p>
|
|
590
735
|
</a>
|
|
591
736
|
))}
|
|
592
737
|
</div>
|
|
593
738
|
</section>`);
|
|
594
739
|
}
|
|
595
740
|
if (ctx.template.key === "restaurant") {
|
|
596
|
-
return shell(`<section className="mx-auto max-w-4xl px-6 py-
|
|
597
|
-
<p className="
|
|
598
|
-
<h1 className="text-
|
|
599
|
-
<p className="mx-auto mt-6 max-w-xl
|
|
600
|
-
<
|
|
741
|
+
return shell(`<section className="relative mx-auto max-w-4xl px-6 py-32 text-center sm:py-40">
|
|
742
|
+
<p className="text-sm font-semibold uppercase tracking-[0.35em] text-faint">Est. 2026 \xB7 Natural wine bar & kitchen</p>
|
|
743
|
+
<h1 className="text-display mt-5"><span className="gradient-text">${n}</span></h1>
|
|
744
|
+
<p className="lead mx-auto mt-6 max-w-xl">${ctx.template.siteDescription}</p>
|
|
745
|
+
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
|
746
|
+
<a href="/reservations" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Reserve a table</a>
|
|
747
|
+
<a href="/menu" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">View the menu</a>
|
|
748
|
+
</div>
|
|
601
749
|
</section>
|
|
602
|
-
<section id="menu" className="mx-auto max-w-4xl px-6 py-
|
|
603
|
-
<
|
|
604
|
-
<div className="grid gap-6 sm:grid-cols-2">
|
|
750
|
+
<section id="menu" className="mx-auto max-w-4xl px-6 py-20">
|
|
751
|
+
<div className="mb-12 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">From the pass</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Tonight's plates</h2></div>
|
|
752
|
+
<div className="grid gap-x-12 gap-y-6 sm:grid-cols-2">
|
|
605
753
|
{[
|
|
606
|
-
{ n: "Charred leeks", p: "$14" }, { n: "Handmade tagliatelle", p: "$22" },
|
|
607
|
-
{ n: "Wood-fired trout", p: "$28" }, { n: "
|
|
754
|
+
{ n: "Charred leeks, hazelnut & aged pecorino", p: "$14" }, { n: "Handmade tagliatelle, brown butter & sage", p: "$22" },
|
|
755
|
+
{ n: "Wood-fired trout, fennel & burnt lemon", p: "$28" }, { n: "Dry-aged sirloin, bone marrow butter", p: "$34" },
|
|
756
|
+
{ n: "Roasted heritage carrots, dukkah & yoghurt", p: "$13" }, { n: "Olive oil & almond cake, cr\xE8me fra\xEEche", p: "$11" },
|
|
608
757
|
].map((d) => (
|
|
609
|
-
<div key={d.n} className="flex items-baseline
|
|
610
|
-
<
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
</div>
|
|
614
|
-
<span className="shrink-0 gradient-text font-bold">{d.p}</span>
|
|
758
|
+
<div key={d.n} className="flex items-baseline gap-3 border-b border-dashed border-hairline pb-4">
|
|
759
|
+
<h3 className="font-semibold tracking-tight">{d.n}</h3>
|
|
760
|
+
<span className="flex-1" />
|
|
761
|
+
<span className="shrink-0 gradient-text font-bold tabular-nums">{d.p}</span>
|
|
615
762
|
</div>
|
|
616
763
|
))}
|
|
617
764
|
</div>
|
|
618
|
-
|
|
619
|
-
<section id="reserve" className="mx-auto max-w-2xl px-6 py-24 text-center">
|
|
620
|
-
<h2 className="text-3xl font-bold">Join us</h2>
|
|
621
|
-
<p className="mt-3 text-muted">Open Wed\u2013Sun, 5pm till late. Walk-ins welcome; bookings recommended.</p>
|
|
622
|
-
<a href="tel:+10000000000" className="mt-8 inline-block rounded-full border border-hairline px-8 py-3 font-semibold hover:bg-surface">Call to book</a>
|
|
765
|
+
<p className="mt-10 text-center text-sm text-muted">Menu changes with the seasons \xB7 ask us about tonight's natural wine pairings.</p>
|
|
623
766
|
</section>`);
|
|
624
767
|
}
|
|
625
|
-
return shell(`<section className="mx-auto max-w-4xl px-6 py-
|
|
626
|
-
<
|
|
627
|
-
<h1 className="text-
|
|
628
|
-
<p className="mx-auto mt-6 max-w-2xl
|
|
629
|
-
<div className="mt-10 flex justify-center gap-
|
|
630
|
-
<a href="
|
|
631
|
-
<a href="
|
|
768
|
+
return shell(`<section className="relative mx-auto max-w-4xl px-6 py-32 text-center sm:py-40">
|
|
769
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted"><span aria-hidden className="h-1.5 w-1.5 rounded-full gradient-bg" /> New \xB7 Realtime dashboards are live</span>
|
|
770
|
+
<h1 className="text-display mt-7">Ship faster with <span className="gradient-text">${n}</span></h1>
|
|
771
|
+
<p className="lead mx-auto mt-6 max-w-2xl">${ctx.template.siteDescription}</p>
|
|
772
|
+
<div className="mt-10 flex flex-wrap justify-center gap-3">
|
|
773
|
+
<a href="/pricing" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Start free</a>
|
|
774
|
+
<a href="/features" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">See features</a>
|
|
632
775
|
</div>
|
|
776
|
+
<p className="mt-5 text-sm text-faint">No credit card \xB7 Free forever plan \xB7 SOC 2-ready</p>
|
|
633
777
|
</section>
|
|
634
|
-
<section id="features" className="mx-auto max-w-6xl px-6 py-
|
|
778
|
+
<section id="features" className="mx-auto max-w-6xl px-6 py-20">
|
|
779
|
+
<div className="mb-12 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Why teams switch</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Everything in one fast place</h2></div>
|
|
635
780
|
<div className="grid gap-6 md:grid-cols-3">
|
|
636
781
|
{[
|
|
637
|
-
{ t: "
|
|
638
|
-
{ t: "Secure", d: "Hardened headers and
|
|
639
|
-
{ t: "
|
|
782
|
+
{ t: "Realtime analytics", d: "Live dashboards on the edge \u2014 global by default, no waiting for pipelines.", e: "\u26A1" },
|
|
783
|
+
{ t: "Secure by default", d: "Hardened headers, SSO and audit logs out of the box. SOC 2-ready.", e: "\u{1F512}" },
|
|
784
|
+
{ t: "Scales with you", d: "From your first user to your millionth, with usage-based billing built in.", e: "\u{1F4C8}" },
|
|
640
785
|
].map((f) => (
|
|
641
|
-
<div key={f.t} className="
|
|
642
|
-
<
|
|
643
|
-
<
|
|
786
|
+
<div key={f.t} className="card p-8">
|
|
787
|
+
<div className="mb-5 inline-flex h-12 w-12 items-center justify-center rounded-2xl gradient-bg text-2xl accent-glow">{f.e}</div>
|
|
788
|
+
<h3 className="text-xl font-bold tracking-tight">{f.t}</h3>
|
|
789
|
+
<p className="mt-2 leading-relaxed text-muted">{f.d}</p>
|
|
644
790
|
</div>
|
|
645
791
|
))}
|
|
646
792
|
</div>
|
|
647
|
-
</section>
|
|
648
|
-
<section id="pricing" className="mx-auto max-w-md px-6 py-24 text-center">
|
|
649
|
-
<div className="rounded-3xl border border-hairline bg-surface p-10">
|
|
650
|
-
<h2 className="text-3xl font-bold">Pro</h2>
|
|
651
|
-
<p className="mt-2 text-5xl font-black gradient-text">$29<span className="text-lg text-faint">/mo</span></p>
|
|
652
|
-
<a href="#" className="mt-8 inline-block w-full gradient-bg rounded-full px-8 py-3 font-semibold text-black">Get started</a>
|
|
653
|
-
</div>
|
|
654
793
|
</section>`);
|
|
655
794
|
}
|
|
656
795
|
var glyph = (ctx) => (ctx.template.siteName.trim()[0] ?? "A").toUpperCase();
|
|
@@ -876,7 +1015,7 @@ var careersPage = (ctx) => {
|
|
|
876
1015
|
].map((j) => (
|
|
877
1016
|
<div key={j.role} className="flex flex-wrap items-center justify-between gap-3 rounded-2xl border border-hairline bg-surface p-5">
|
|
878
1017
|
<div><h3 className="font-semibold">{j.role}</h3><p className="text-sm text-muted">{j.team}</p></div>
|
|
879
|
-
<div className="flex items-center gap-3"><Badge>{j.type}</Badge><a href="/contact" className="rounded-full gradient-bg px-4 py-2 text-sm font-semibold
|
|
1018
|
+
<div className="flex items-center gap-3"><Badge>{j.type}</Badge><a href="/contact" className="rounded-full gradient-bg px-4 py-2 text-sm font-semibold on-accent">Apply</a></div>
|
|
880
1019
|
</div>
|
|
881
1020
|
))}
|
|
882
1021
|
</div>
|
|
@@ -973,7 +1112,7 @@ var reservationsPage = (ctx) => {
|
|
|
973
1112
|
<div className="mx-auto max-w-2xl rounded-3xl border border-hairline bg-surface p-8 text-center">
|
|
974
1113
|
<h2 className="text-2xl font-bold">Book by phone or online</h2>
|
|
975
1114
|
<p className="mt-3 text-muted">Call us on <span className="font-semibold text-fg">+1 (555) 012-3456</span> or reserve online in seconds.</p>
|
|
976
|
-
<a href="/contact" className="mt-6 inline-block rounded-full gradient-bg px-8 py-3 font-semibold
|
|
1115
|
+
<a href="/contact" className="mt-6 inline-block rounded-full gradient-bg px-8 py-3 font-semibold on-accent">Reserve a table</a>
|
|
977
1116
|
</div>
|
|
978
1117
|
</Section>`,
|
|
979
1118
|
cta: { title: "Planning something special?", subtitle: "Ask us about private dining and set menus.", label: "Enquire now", href: "/contact" }
|
|
@@ -1151,7 +1290,7 @@ export function ContactForm() {
|
|
|
1151
1290
|
{err("_form") && <p className={errCls}>{err("_form")}</p>}
|
|
1152
1291
|
<button
|
|
1153
1292
|
disabled={pending}
|
|
1154
|
-
className="gradient-bg rounded-full px-8 py-3 font-semibold
|
|
1293
|
+
className="gradient-bg rounded-full px-8 py-3 font-semibold on-accent disabled:opacity-60"
|
|
1155
1294
|
>
|
|
1156
1295
|
{pending ? "Sending\u2026" : "Send message"}
|
|
1157
1296
|
</button>
|
|
@@ -1281,7 +1420,7 @@ export function DocsSidebar({ nav }: { nav: { group: string; items: DocMeta[] }[
|
|
|
1281
1420
|
<li key={d.slug}>
|
|
1282
1421
|
<Link
|
|
1283
1422
|
href={href}
|
|
1284
|
-
className={\`block rounded-lg px-3 py-1.5 transition \${active ? "gradient-bg font-semibold
|
|
1423
|
+
className={\`block rounded-lg px-3 py-1.5 transition \${active ? "gradient-bg font-semibold on-accent" : "text-muted hover:bg-surface hover:text-fg"}\`}
|
|
1285
1424
|
>
|
|
1286
1425
|
{d.title}
|
|
1287
1426
|
</Link>
|
|
@@ -1900,12 +2039,14 @@ import { useIsMounted } from "@lacspace/hooks";` : "";
|
|
|
1900
2039
|
<Link href="/cart" aria-label="Cart" className="relative inline-flex h-9 w-9 items-center justify-center rounded-full border border-hairline text-muted transition hover:text-fg">
|
|
1901
2040
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden><circle cx="9" cy="21" r="1" /><circle cx="20" cy="21" r="1" /><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" /></svg>
|
|
1902
2041
|
{mounted() && count > 0 ? (
|
|
1903
|
-
<span className="absolute -right-1 -top-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full gradient-bg px-1 text-[10px] font-bold
|
|
2042
|
+
<span className="absolute -right-1 -top-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full gradient-bg px-1 text-[10px] font-bold on-accent">{count}</span>
|
|
1904
2043
|
) : null}
|
|
1905
2044
|
</Link>` : "";
|
|
1906
2045
|
return `"use client";
|
|
1907
2046
|
|
|
2047
|
+
import { useEffect, useState } from "react";
|
|
1908
2048
|
import Link from "next/link";
|
|
2049
|
+
import { usePathname } from "next/navigation";
|
|
1909
2050
|
import { useUI } from "@/lib/store";
|
|
1910
2051
|
import { ThemeToggle } from "./theme-toggle";${cartImport}
|
|
1911
2052
|
|
|
@@ -1914,16 +2055,28 @@ const LINKS = [${linkLiteral(pagesFor(ctx).filter((p) => p.nav))}];
|
|
|
1914
2055
|
export function SiteHeader() {
|
|
1915
2056
|
const open = useUI((s) => s.navOpen);
|
|
1916
2057
|
const toggle = useUI((s) => s.toggleNav);
|
|
1917
|
-
const setOpen = useUI((s) => s.setNavOpen)
|
|
2058
|
+
const setOpen = useUI((s) => s.setNavOpen);
|
|
2059
|
+
const pathname = usePathname();${cartHook}
|
|
2060
|
+
// Sticky glass header that tightens and gains a hairline once you scroll.
|
|
2061
|
+
const [scrolled, setScrolled] = useState(false);
|
|
2062
|
+
useEffect(() => {
|
|
2063
|
+
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
2064
|
+
onScroll();
|
|
2065
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
2066
|
+
return () => window.removeEventListener("scroll", onScroll);
|
|
2067
|
+
}, []);
|
|
1918
2068
|
return (
|
|
1919
|
-
<header className="sticky top-0 z-40 border-b border-
|
|
1920
|
-
<nav className="mx-auto flex max-w-6xl items-center justify-between px-6 py-
|
|
1921
|
-
<Link href="/" className="text-lg font-black gradient-text">${ctx.template.siteName}</Link>
|
|
1922
|
-
<div className="hidden items-center gap-
|
|
1923
|
-
{LINKS.map((l) =>
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
2069
|
+
<header className={"sticky top-0 z-40 transition-all duration-300 " + (scrolled ? "glass shadow-sm" : "border-b border-transparent bg-transparent")}>
|
|
2070
|
+
<nav className={"mx-auto flex max-w-6xl items-center justify-between px-6 transition-all duration-300 " + (scrolled ? "py-3" : "py-5")}>
|
|
2071
|
+
<Link href="/" className="text-lg font-black tracking-tight gradient-text">${ctx.template.siteName}</Link>
|
|
2072
|
+
<div className="hidden items-center gap-1 md:flex">
|
|
2073
|
+
{LINKS.map((l) => {
|
|
2074
|
+
const active = pathname === l.href;
|
|
2075
|
+
return (
|
|
2076
|
+
<Link key={l.href} href={l.href} className={"rounded-full px-3.5 py-1.5 text-sm font-medium transition " + (active ? "bg-surface text-fg" : "text-muted hover:bg-surface hover:text-fg")}>{l.label}</Link>
|
|
2077
|
+
);
|
|
2078
|
+
})}
|
|
2079
|
+
<div className="ml-2 flex items-center gap-2"><ThemeToggle />${cartBtn}</div>
|
|
1927
2080
|
</div>
|
|
1928
2081
|
<div className="flex items-center gap-2 md:hidden">
|
|
1929
2082
|
<ThemeToggle />${cartBtn}
|
|
@@ -1933,10 +2086,10 @@ export function SiteHeader() {
|
|
|
1933
2086
|
</div>
|
|
1934
2087
|
</nav>
|
|
1935
2088
|
{open ? (
|
|
1936
|
-
<div className="border-t border-hairline md:hidden">
|
|
2089
|
+
<div className="glass border-t border-hairline md:hidden">
|
|
1937
2090
|
<div className="mx-auto flex max-w-6xl flex-col gap-1 px-6 py-3">
|
|
1938
2091
|
{LINKS.map((l) => (
|
|
1939
|
-
<Link key={l.href} href={l.href} onClick={() => setOpen(false)} className="rounded-
|
|
2092
|
+
<Link key={l.href} href={l.href} onClick={() => setOpen(false)} className="rounded-xl px-3 py-2 text-muted transition hover:bg-surface hover:text-fg">{l.label}</Link>
|
|
1940
2093
|
))}
|
|
1941
2094
|
</div>
|
|
1942
2095
|
</div>
|
|
@@ -1951,22 +2104,32 @@ var siteFooter = (ctx) => {
|
|
|
1951
2104
|
const groups = ["Product", "Company", "Resources"].map((title) => ({ title, items: pages.filter((p) => p.group === title) })).filter((g) => g.items.length > 0);
|
|
1952
2105
|
const groupsLiteral = groups.map((g) => `{ title: ${JSON.stringify(g.title)}, links: [${linkLiteral(g.items)}] }`).join(", ");
|
|
1953
2106
|
return `import Link from "next/link";
|
|
2107
|
+
import { Newsletter } from "@/components/ui";
|
|
1954
2108
|
|
|
1955
2109
|
const GROUPS = [${groupsLiteral}];
|
|
1956
2110
|
|
|
1957
2111
|
export function SiteFooter() {
|
|
1958
2112
|
return (
|
|
1959
|
-
<footer className="border-t border-hairline">
|
|
1960
|
-
<div className="mx-auto max-w-6xl px-6
|
|
1961
|
-
|
|
2113
|
+
<footer className="relative mt-24 border-t border-hairline">
|
|
2114
|
+
<div className="mx-auto max-w-6xl px-6">
|
|
2115
|
+
{/* newsletter island \u2014 floats over the fold between page and footer */}
|
|
2116
|
+
<div className="-mt-16 mb-16">
|
|
2117
|
+
<Newsletter title="Stay in the loop" subtitle="Occasional updates from ${ctx.template.siteName}. No spam \u2014 unsubscribe anytime." />
|
|
2118
|
+
</div>
|
|
2119
|
+
<div className="grid gap-10 pb-4 md:grid-cols-[1.4fr_1fr_1fr_1fr]">
|
|
1962
2120
|
<div>
|
|
1963
|
-
<div className="text-lg font-black gradient-text">${ctx.template.siteName}</div>
|
|
1964
|
-
<p className="mt-3 max-w-xs text-sm text-muted">${ctx.template.siteDescription}</p>
|
|
2121
|
+
<div className="text-lg font-black tracking-tight gradient-text">${ctx.template.siteName}</div>
|
|
2122
|
+
<p className="mt-3 max-w-xs text-sm leading-relaxed text-muted">${ctx.template.siteDescription}</p>
|
|
2123
|
+
<div className="mt-5 flex gap-2">
|
|
2124
|
+
{["\u{1D54F}", "in", "gh"].map((s) => (
|
|
2125
|
+
<span key={s} className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-hairline text-xs font-semibold text-muted transition hover:border-[color:var(--accent-to)] hover:text-fg">{s}</span>
|
|
2126
|
+
))}
|
|
2127
|
+
</div>
|
|
1965
2128
|
</div>
|
|
1966
2129
|
{GROUPS.map((g) => (
|
|
1967
2130
|
<div key={g.title}>
|
|
1968
|
-
<div className="text-
|
|
1969
|
-
<ul className="mt-
|
|
2131
|
+
<div className="text-xs font-semibold uppercase tracking-widest text-faint">{g.title}</div>
|
|
2132
|
+
<ul className="mt-4 space-y-2.5 text-sm text-muted">
|
|
1970
2133
|
{g.links.map((l) => (
|
|
1971
2134
|
<li key={l.href}><Link href={l.href} className="transition hover:text-fg">{l.label}</Link></li>
|
|
1972
2135
|
))}
|
|
@@ -1974,7 +2137,7 @@ export function SiteFooter() {
|
|
|
1974
2137
|
</div>
|
|
1975
2138
|
))}
|
|
1976
2139
|
</div>
|
|
1977
|
-
<div className="mt-12 flex flex-col gap-3 border-t border-hairline
|
|
2140
|
+
<div className="mt-12 flex flex-col gap-3 border-t border-hairline py-8 text-sm text-faint sm:flex-row sm:items-center sm:justify-between">
|
|
1978
2141
|
<span>\xA9 {new Date().getFullYear()} ${ctx.template.siteName}. All rights reserved.</span>
|
|
1979
2142
|
<span>Built with <a href="https://lacspace.com/packages" className="text-muted transition hover:text-fg">the Lacspace React Kit</a>.</span>
|
|
1980
2143
|
</div>
|
|
@@ -1998,10 +2161,10 @@ export function AnnouncementBar() {
|
|
|
1998
2161
|
if (!mounted() || dismissed) return null;
|
|
1999
2162
|
|
|
2000
2163
|
return (
|
|
2001
|
-
<div className="relative gradient-bg px-10 py-2 text-center text-sm font-medium
|
|
2164
|
+
<div className="relative gradient-bg px-10 py-2 text-center text-sm font-medium on-accent">
|
|
2002
2165
|
\u2728 Built with the Lacspace React Kit \u2014{" "}
|
|
2003
2166
|
<a href="https://lacspace.com/packages" className="underline underline-offset-2">explore the packages</a>
|
|
2004
|
-
<button type="button" aria-label="Dismiss" onClick={dismiss} className="absolute right-3 top-1/2 -translate-y-1/2 text-lg leading-none
|
|
2167
|
+
<button type="button" aria-label="Dismiss" onClick={dismiss} className="absolute right-3 top-1/2 -translate-y-1/2 text-lg leading-none on-accent/60 hover:on-accent">\xD7</button>
|
|
2005
2168
|
</div>
|
|
2006
2169
|
);
|
|
2007
2170
|
}
|
|
@@ -2022,7 +2185,7 @@ export function UnderDevelopment({ title = "This page", path }: { title?: string
|
|
|
2022
2185
|
<div className="h-full w-1/3 gradient-bg" style={{ animation: "loadbar 1.8s ease-in-out infinite" }} />
|
|
2023
2186
|
</div>
|
|
2024
2187
|
<div className="mt-10 flex flex-wrap items-center justify-center gap-3">
|
|
2025
|
-
<Link href="/" className="gradient-bg rounded-full px-6 py-3 font-semibold
|
|
2188
|
+
<Link href="/" className="gradient-bg rounded-full px-6 py-3 font-semibold on-accent">Back home</Link>
|
|
2026
2189
|
<Link href="/contact" className="rounded-full border border-hairline px-6 py-3 font-semibold transition hover:bg-surface">Get in touch</Link>
|
|
2027
2190
|
</div>
|
|
2028
2191
|
{path ? (
|
|
@@ -2184,29 +2347,48 @@ var builtWithSection = (ctx) => {
|
|
|
2184
2347
|
};
|
|
2185
2348
|
var dashboardShell = (ctx) => {
|
|
2186
2349
|
const nav = [{ path: "/", label: "Overview" }, ...pagesFor(ctx).filter((p) => p.nav)];
|
|
2187
|
-
return `
|
|
2350
|
+
return `"use client";
|
|
2351
|
+
import Link from "next/link";
|
|
2352
|
+
import { usePathname } from "next/navigation";
|
|
2188
2353
|
import type { ReactNode } from "react";
|
|
2189
2354
|
import { ThemeToggle } from "./theme-toggle";
|
|
2190
2355
|
|
|
2191
2356
|
const NAV = [${linkLiteral(nav)}];
|
|
2192
2357
|
|
|
2193
2358
|
export function DashboardShell({ title, subtitle, children }: { title: string; subtitle?: string; children: ReactNode }) {
|
|
2359
|
+
const pathname = usePathname();
|
|
2194
2360
|
return (
|
|
2195
2361
|
<div className="flex min-h-screen">
|
|
2196
|
-
<aside className="hidden w-
|
|
2197
|
-
<div className="mb-8 flex items-center justify-between">
|
|
2198
|
-
<Link href="/" className="text-lg font-black gradient-text">${ctx.template.siteName}</Link>
|
|
2362
|
+
<aside className="sticky top-0 hidden h-screen w-60 shrink-0 flex-col border-r border-hairline bg-surface/40 p-5 md:flex">
|
|
2363
|
+
<div className="mb-8 flex items-center justify-between px-2">
|
|
2364
|
+
<Link href="/" className="text-lg font-black tracking-tight gradient-text">${ctx.template.siteName}</Link>
|
|
2199
2365
|
<ThemeToggle />
|
|
2200
2366
|
</div>
|
|
2201
|
-
<nav className="space-y-1 text-sm
|
|
2202
|
-
{NAV.map((l) =>
|
|
2203
|
-
|
|
2204
|
-
|
|
2367
|
+
<nav className="flex-1 space-y-1 text-sm">
|
|
2368
|
+
{NAV.map((l) => {
|
|
2369
|
+
const active = pathname === l.href;
|
|
2370
|
+
return (
|
|
2371
|
+
<Link key={l.href} href={l.href} className={"flex items-center gap-2 rounded-xl px-3 py-2 font-medium transition " + (active ? "gradient-bg on-accent shadow-sm" : "text-muted hover:bg-surface hover:text-fg")}>
|
|
2372
|
+
<span aria-hidden className={"h-1.5 w-1.5 rounded-full " + (active ? "bg-[color:var(--on-accent)]" : "bg-current opacity-40")} />
|
|
2373
|
+
{l.label}
|
|
2374
|
+
</Link>
|
|
2375
|
+
);
|
|
2376
|
+
})}
|
|
2205
2377
|
</nav>
|
|
2378
|
+
<div className="mt-4 rounded-2xl border border-hairline p-4 text-xs text-muted">
|
|
2379
|
+
<p className="font-semibold text-fg">Need a hand?</p>
|
|
2380
|
+
<p className="mt-1">Read the docs or reach the team.</p>
|
|
2381
|
+
<Link href="/help" className="mt-3 inline-block rounded-full gradient-bg px-3 py-1.5 font-semibold on-accent">Get help</Link>
|
|
2382
|
+
</div>
|
|
2206
2383
|
</aside>
|
|
2207
|
-
<main className="flex-1 p-6 md:p-10">
|
|
2208
|
-
<
|
|
2209
|
-
|
|
2384
|
+
<main className="min-w-0 flex-1 p-6 md:p-10">
|
|
2385
|
+
<div className="flex flex-wrap items-end justify-between gap-4">
|
|
2386
|
+
<div>
|
|
2387
|
+
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">{title}</h1>
|
|
2388
|
+
{subtitle ? <p className="mt-1.5 text-muted">{subtitle}</p> : null}
|
|
2389
|
+
</div>
|
|
2390
|
+
<div className="md:hidden"><ThemeToggle /></div>
|
|
2391
|
+
</div>
|
|
2210
2392
|
<div className="mt-8">{children}</div>
|
|
2211
2393
|
</main>
|
|
2212
2394
|
</div>
|
|
@@ -2231,19 +2413,25 @@ export default function Home() {
|
|
|
2231
2413
|
</div>
|
|
2232
2414
|
<div className="mt-6 grid gap-6 lg:grid-cols-3">
|
|
2233
2415
|
<div className="rounded-2xl border border-hairline bg-surface p-6 lg:col-span-2">
|
|
2234
|
-
<div className="mb-
|
|
2235
|
-
<h2 className="font-semibold">Revenue</h2>
|
|
2236
|
-
<span className="text-
|
|
2416
|
+
<div className="mb-5 flex items-center justify-between">
|
|
2417
|
+
<div><h2 className="font-semibold tracking-tight">Revenue</h2><p className="text-sm text-muted">Last 12 months</p></div>
|
|
2418
|
+
<span className="rounded-full bg-emerald-500/10 px-2.5 py-1 text-xs font-semibold text-emerald-400">\u25B2 12.4%</span>
|
|
2237
2419
|
</div>
|
|
2238
2420
|
<AreaChart data={[12, 18, 15, 22, 20, 28, 26, 34, 30, 38, 42, 48]} />
|
|
2239
2421
|
</div>
|
|
2240
2422
|
<div className="rounded-2xl border border-hairline bg-surface p-6">
|
|
2241
|
-
<div className="mb-
|
|
2242
|
-
<div className="space-y-
|
|
2243
|
-
{[
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2423
|
+
<div className="mb-5 font-semibold tracking-tight">Recent activity</div>
|
|
2424
|
+
<div className="space-y-4">
|
|
2425
|
+
{[
|
|
2426
|
+
{ who: "Ava Chen", what: "upgraded to Pro", when: "2m ago" },
|
|
2427
|
+
{ who: "Order #4821", what: "was paid", when: "18m ago" },
|
|
2428
|
+
{ who: "Liam Patel", what: "started a trial", when: "1h ago" },
|
|
2429
|
+
{ who: "Refund #204", what: "was issued", when: "3h ago" },
|
|
2430
|
+
].map((e) => (
|
|
2431
|
+
<div key={e.who} className="flex items-center gap-3 text-sm">
|
|
2432
|
+
<span className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full gradient-bg text-xs font-bold on-accent">{e.who.slice(0, 1)}</span>
|
|
2433
|
+
<span className="min-w-0 flex-1 truncate text-muted"><span className="font-medium text-fg">{e.who}</span> {e.what}</span>
|
|
2434
|
+
<span className="shrink-0 text-faint">{e.when}</span>
|
|
2247
2435
|
</div>
|
|
2248
2436
|
))}
|
|
2249
2437
|
</div>
|
|
@@ -2290,7 +2478,7 @@ var pricingPage = (ctx) => pageFile({
|
|
|
2290
2478
|
{ name: "Scale", price: "Custom", period: "", tagline: "For organizations", features: ["SSO & SAML", "Dedicated support", "SLA & audit logs", "Guided onboarding"], cta: "Contact sales", highlight: false },
|
|
2291
2479
|
].map((tier) => (
|
|
2292
2480
|
<div key={tier.name} className={"flex flex-col rounded-3xl border p-8 " + (tier.highlight ? "border-transparent bg-surface ring-2 ring-[color:var(--accent-to)]" : "border-hairline bg-surface")}>
|
|
2293
|
-
{tier.highlight ? <span className="mb-4 inline-block w-fit rounded-full gradient-bg px-3 py-1 text-xs font-bold
|
|
2481
|
+
{tier.highlight ? <span className="mb-4 inline-block w-fit rounded-full gradient-bg px-3 py-1 text-xs font-bold on-accent">Most popular</span> : null}
|
|
2294
2482
|
<h2 className="text-lg font-semibold">{tier.name}</h2>
|
|
2295
2483
|
<p className="mt-1 text-sm text-muted">{tier.tagline}</p>
|
|
2296
2484
|
<div className="mt-6 flex items-baseline gap-1">
|
|
@@ -2302,7 +2490,7 @@ var pricingPage = (ctx) => pageFile({
|
|
|
2302
2490
|
<li key={f} className="flex items-center gap-2 text-muted"><span className="text-[color:var(--accent-to)]">\u2713</span> {f}</li>
|
|
2303
2491
|
))}
|
|
2304
2492
|
</ul>
|
|
2305
|
-
<Link href="/contact" className={"mt-8 rounded-full px-6 py-3 text-center font-semibold transition " + (tier.highlight ? "gradient-bg
|
|
2493
|
+
<Link href="/contact" className={"mt-8 rounded-full px-6 py-3 text-center font-semibold transition " + (tier.highlight ? "gradient-bg on-accent" : "border border-hairline hover:bg-app")}>{tier.cta}</Link>
|
|
2306
2494
|
</div>
|
|
2307
2495
|
))}
|
|
2308
2496
|
</div>
|
|
@@ -2599,7 +2787,7 @@ export function ProductGrid() {
|
|
|
2599
2787
|
<div className="mb-4 flex aspect-square items-center justify-center rounded-xl gradient-bg text-5xl">{p.emoji}</div>
|
|
2600
2788
|
<h3 className="font-semibold">{p.name}</h3>
|
|
2601
2789
|
<div className="mt-1 text-muted">{"$" + p.price.toFixed(2)}</div>
|
|
2602
|
-
<button type="button" onClick={() => add({ id: p.id, name: p.name, price: p.price })} className="mt-4 rounded-full gradient-bg px-4 py-2 text-sm font-semibold
|
|
2790
|
+
<button type="button" onClick={() => add({ id: p.id, name: p.name, price: p.price })} className="mt-4 rounded-full gradient-bg px-4 py-2 text-sm font-semibold on-accent transition hover:opacity-90">Add to bag</button>
|
|
2603
2791
|
</div>
|
|
2604
2792
|
))}
|
|
2605
2793
|
</div>
|
|
@@ -2624,7 +2812,7 @@ export function CartView() {
|
|
|
2624
2812
|
return (
|
|
2625
2813
|
<div className="mx-auto max-w-md py-16 text-center">
|
|
2626
2814
|
<p className="text-lg text-muted">Your bag is empty.</p>
|
|
2627
|
-
<Link href="/shop" className="mt-6 inline-block rounded-full gradient-bg px-6 py-3 font-semibold
|
|
2815
|
+
<Link href="/shop" className="mt-6 inline-block rounded-full gradient-bg px-6 py-3 font-semibold on-accent">Browse the shop</Link>
|
|
2628
2816
|
</div>
|
|
2629
2817
|
);
|
|
2630
2818
|
}
|
|
@@ -2648,7 +2836,7 @@ export function CartView() {
|
|
|
2648
2836
|
<button type="button" onClick={clear} className="text-sm text-muted transition hover:text-fg">Clear bag</button>
|
|
2649
2837
|
<span className="text-lg font-bold">Total {"$" + total.toFixed(2)}</span>
|
|
2650
2838
|
</div>
|
|
2651
|
-
<button type="button" className="mt-8 w-full rounded-full gradient-bg px-6 py-3 font-semibold
|
|
2839
|
+
<button type="button" className="mt-8 w-full rounded-full gradient-bg px-6 py-3 font-semibold on-accent">Checkout</button>
|
|
2652
2840
|
</div>
|
|
2653
2841
|
);
|
|
2654
2842
|
}
|
|
@@ -2875,7 +3063,7 @@ export function CartButton() {
|
|
|
2875
3063
|
<Link href="/cart" aria-label="Cart" className="relative inline-flex h-9 w-9 items-center justify-center rounded-full border border-hairline text-muted transition hover:text-fg">
|
|
2876
3064
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden><circle cx="9" cy="21" r="1" /><circle cx="20" cy="21" r="1" /><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6" /></svg>
|
|
2877
3065
|
{mounted() && count > 0 ? (
|
|
2878
|
-
<span className="absolute -right-1 -top-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full gradient-bg px-1 text-[10px] font-bold
|
|
3066
|
+
<span className="absolute -right-1 -top-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full gradient-bg px-1 text-[10px] font-bold on-accent">{count}</span>
|
|
2879
3067
|
) : null}
|
|
2880
3068
|
</Link>
|
|
2881
3069
|
);
|
|
@@ -2911,7 +3099,7 @@ export function AddToCart({ product, className = "" }: { product: AddToCartProdu
|
|
|
2911
3099
|
setAdded(true);
|
|
2912
3100
|
window.setTimeout(() => setAdded(false), 1200);
|
|
2913
3101
|
}}
|
|
2914
|
-
className={"rounded-full gradient-bg px-4 py-2 text-sm font-semibold
|
|
3102
|
+
className={"rounded-full gradient-bg px-4 py-2.5 text-sm font-semibold on-accent transition hover:-translate-y-0.5 " + className}
|
|
2915
3103
|
>
|
|
2916
3104
|
{added ? "Added \\u2713" : "Add to cart"}
|
|
2917
3105
|
</button>
|
|
@@ -2920,7 +3108,9 @@ export function AddToCart({ product, className = "" }: { product: AddToCartProdu
|
|
|
2920
3108
|
`;
|
|
2921
3109
|
var marketHeader = (ctx) => `"use client";
|
|
2922
3110
|
|
|
3111
|
+
import { useEffect, useState } from "react";
|
|
2923
3112
|
import Link from "next/link";
|
|
3113
|
+
import { usePathname } from "next/navigation";
|
|
2924
3114
|
import { useUI } from "@/lib/store";
|
|
2925
3115
|
import { ThemeToggle } from "./theme-toggle";
|
|
2926
3116
|
import { CartButton } from "./cart-button";
|
|
@@ -2931,16 +3121,26 @@ export function SiteHeader() {
|
|
|
2931
3121
|
const open = useUI((s) => s.navOpen);
|
|
2932
3122
|
const toggle = useUI((s) => s.toggleNav);
|
|
2933
3123
|
const setOpen = useUI((s) => s.setNavOpen);
|
|
3124
|
+
const pathname = usePathname();
|
|
3125
|
+
const [scrolled, setScrolled] = useState(false);
|
|
3126
|
+
useEffect(() => {
|
|
3127
|
+
const onScroll = () => setScrolled(window.scrollY > 8);
|
|
3128
|
+
onScroll();
|
|
3129
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
3130
|
+
return () => window.removeEventListener("scroll", onScroll);
|
|
3131
|
+
}, []);
|
|
2934
3132
|
return (
|
|
2935
|
-
<header className="sticky top-0 z-40 border-b border-
|
|
2936
|
-
<nav className="mx-auto flex max-w-6xl items-center justify-between px-6 py-
|
|
2937
|
-
<Link href="/" className="text-lg font-black gradient-text">${ctx.template.siteName}</Link>
|
|
2938
|
-
<div className="hidden items-center gap-
|
|
2939
|
-
{LINKS.map((l) =>
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
3133
|
+
<header className={"sticky top-0 z-40 transition-all duration-300 " + (scrolled ? "glass shadow-sm" : "border-b border-transparent bg-transparent")}>
|
|
3134
|
+
<nav className={"mx-auto flex max-w-6xl items-center justify-between px-6 transition-all duration-300 " + (scrolled ? "py-3" : "py-5")}>
|
|
3135
|
+
<Link href="/" className="text-lg font-black tracking-tight gradient-text">${ctx.template.siteName}</Link>
|
|
3136
|
+
<div className="hidden items-center gap-1 md:flex">
|
|
3137
|
+
{LINKS.map((l) => {
|
|
3138
|
+
const active = pathname === l.href;
|
|
3139
|
+
return (
|
|
3140
|
+
<Link key={l.href} href={l.href} className={"rounded-full px-3.5 py-1.5 text-sm font-medium transition " + (active ? "bg-surface text-fg" : "text-muted hover:bg-surface hover:text-fg")}>{l.label}</Link>
|
|
3141
|
+
);
|
|
3142
|
+
})}
|
|
3143
|
+
<div className="ml-2 flex items-center gap-2"><ThemeToggle /><CartButton /></div>
|
|
2944
3144
|
</div>
|
|
2945
3145
|
<div className="flex items-center gap-2 md:hidden">
|
|
2946
3146
|
<ThemeToggle />
|
|
@@ -2951,10 +3151,10 @@ export function SiteHeader() {
|
|
|
2951
3151
|
</div>
|
|
2952
3152
|
</nav>
|
|
2953
3153
|
{open ? (
|
|
2954
|
-
<div className="border-t border-hairline md:hidden">
|
|
3154
|
+
<div className="glass border-t border-hairline md:hidden">
|
|
2955
3155
|
<div className="mx-auto flex max-w-6xl flex-col gap-1 px-6 py-3">
|
|
2956
3156
|
{LINKS.map((l) => (
|
|
2957
|
-
<Link key={l.href} href={l.href} onClick={() => setOpen(false)} className="rounded-
|
|
3157
|
+
<Link key={l.href} href={l.href} onClick={() => setOpen(false)} className="rounded-xl px-3 py-2 text-muted transition hover:bg-surface hover:text-fg">{l.label}</Link>
|
|
2958
3158
|
))}
|
|
2959
3159
|
</div>
|
|
2960
3160
|
</div>
|
|
@@ -2981,30 +3181,30 @@ export default function Home() {
|
|
|
2981
3181
|
return (
|
|
2982
3182
|
<main className="relative min-h-screen overflow-hidden">
|
|
2983
3183
|
<Aurora />
|
|
2984
|
-
<section className="mx-auto grid max-w-6xl items-center gap-12 px-6 py-
|
|
3184
|
+
<section className="mx-auto grid max-w-6xl items-center gap-12 px-6 py-28 md:grid-cols-2">
|
|
2985
3185
|
<div>
|
|
2986
|
-
<
|
|
2987
|
-
<h1 className="text-
|
|
2988
|
-
<p className="mt-6 max-w-md
|
|
2989
|
-
<div className="mt-
|
|
2990
|
-
<Link href="/shop" className="
|
|
2991
|
-
<Link href="/cart" className="rounded-full border border-hairline px-8 py-3 font-semibold hover:bg-surface">View cart</Link>
|
|
3186
|
+
<span className="glass inline-flex items-center gap-2 rounded-full px-4 py-1.5 text-xs font-semibold text-muted">\u{1F6D2} Cart to checkout, wired end to end</span>
|
|
3187
|
+
<h1 className="text-display mt-6">Shop <span className="gradient-text">${n}</span></h1>
|
|
3188
|
+
<p className="lead mt-6 max-w-md">${ctx.template.siteDescription}</p>
|
|
3189
|
+
<div className="mt-9 flex flex-wrap gap-3">
|
|
3190
|
+
<Link href="/shop" className="shimmer rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Shop the collection</Link>
|
|
3191
|
+
<Link href="/cart" className="rounded-full border border-hairline px-8 py-3.5 font-semibold transition hover:bg-surface">View cart</Link>
|
|
2992
3192
|
</div>
|
|
2993
|
-
<p className="mt-6 text-sm text-muted">Free standard shipping over {formatMoney(500000)}.</p>
|
|
3193
|
+
<p className="mt-6 text-sm text-muted">Free standard shipping over {formatMoney(500000)} \xB7 eSewa & Khalti at checkout.</p>
|
|
2994
3194
|
</div>
|
|
2995
3195
|
<HeroArt />
|
|
2996
3196
|
</section>
|
|
2997
3197
|
<section className="mx-auto max-w-6xl px-6 py-16">
|
|
2998
|
-
<div className="mb-
|
|
2999
|
-
<h2 className="text-3xl font-bold">
|
|
3000
|
-
<Link href="/shop" className="text-sm text-muted underline
|
|
3198
|
+
<div className="mb-12 flex items-end justify-between">
|
|
3199
|
+
<div><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Featured</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Fresh in the store</h2></div>
|
|
3200
|
+
<Link href="/shop" className="hidden text-sm text-muted underline-offset-4 transition hover:text-fg hover:underline sm:block">See all \u2192</Link>
|
|
3001
3201
|
</div>
|
|
3002
3202
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
3003
3203
|
{featured.map((p) => (
|
|
3004
|
-
<div key={p.slug} className="group flex flex-col
|
|
3005
|
-
<Link href={"/product/" + p.slug} className="mb-
|
|
3006
|
-
<Link href={"/product/" + p.slug} className="font-semibold group-hover:
|
|
3007
|
-
<div className="mt-1 text-muted">{formatMoney(p.price)}</div>
|
|
3204
|
+
<div key={p.slug} className="card group flex flex-col p-5">
|
|
3205
|
+
<Link href={"/product/" + p.slug} className="mb-5 flex aspect-square items-center justify-center overflow-hidden rounded-xl gradient-bg text-7xl on-accent transition group-hover:scale-[1.03]">{p.emoji}</Link>
|
|
3206
|
+
<Link href={"/product/" + p.slug} className="font-semibold tracking-tight transition group-hover:text-[color:var(--accent-to)]">{p.name}</Link>
|
|
3207
|
+
<div className="mt-1 font-semibold text-muted">{formatMoney(p.price)}</div>
|
|
3008
3208
|
<div className="mt-4">
|
|
3009
3209
|
<AddToCart product={{ id: p.slug, name: p.name, unitPrice: p.price, weight: p.weight, emoji: p.emoji }} className="w-full" />
|
|
3010
3210
|
</div>
|
|
@@ -3116,7 +3316,7 @@ export default function Page() {
|
|
|
3116
3316
|
<main className="mx-auto min-h-screen max-w-3xl px-6 py-24 text-center">
|
|
3117
3317
|
<h1 className="text-4xl font-bold">Your <span className="gradient-text">cart</span></h1>
|
|
3118
3318
|
<p className="mt-4 text-lg text-muted">It's empty for now.</p>
|
|
3119
|
-
<Link href="/shop" className="mt-8 inline-block rounded-full gradient-bg px-6 py-3 font-semibold
|
|
3319
|
+
<Link href="/shop" className="mt-8 inline-block rounded-full gradient-bg px-6 py-3 font-semibold on-accent">Browse the shop</Link>
|
|
3120
3320
|
</main>
|
|
3121
3321
|
);
|
|
3122
3322
|
}
|
|
@@ -3175,7 +3375,7 @@ export default function Page() {
|
|
|
3175
3375
|
{q.freeShippingRemaining > 0 ? (
|
|
3176
3376
|
<p className="mt-4 text-xs text-muted">Add {formatMoney(q.freeShippingRemaining)} more for free shipping.</p>
|
|
3177
3377
|
) : null}
|
|
3178
|
-
<Link href="/checkout" className="mt-6 block w-full rounded-full gradient-bg px-6 py-3 text-center font-semibold
|
|
3378
|
+
<Link href="/checkout" className="mt-6 block w-full rounded-full gradient-bg px-6 py-3 text-center font-semibold on-accent">Checkout</Link>
|
|
3179
3379
|
<button type="button" onClick={clear} className="mt-3 block w-full text-center text-sm text-muted transition hover:text-fg">Clear cart</button>
|
|
3180
3380
|
</aside>
|
|
3181
3381
|
</div>
|
|
@@ -3215,7 +3415,7 @@ export default function Page() {
|
|
|
3215
3415
|
<main className="mx-auto min-h-screen max-w-3xl px-6 py-24 text-center">
|
|
3216
3416
|
<h1 className="text-4xl font-bold">Checkout</h1>
|
|
3217
3417
|
<p className="mt-4 text-lg text-muted">Your cart is empty.</p>
|
|
3218
|
-
<Link href="/shop" className="mt-8 inline-block rounded-full gradient-bg px-6 py-3 font-semibold
|
|
3418
|
+
<Link href="/shop" className="mt-8 inline-block rounded-full gradient-bg px-6 py-3 font-semibold on-accent">Browse the shop</Link>
|
|
3219
3419
|
</main>
|
|
3220
3420
|
);
|
|
3221
3421
|
}
|
|
@@ -3228,9 +3428,12 @@ export default function Page() {
|
|
|
3228
3428
|
weight: Number((i.meta && (i.meta as Record<string, unknown>).weight) ?? 0),
|
|
3229
3429
|
}));
|
|
3230
3430
|
const q = quote(lines);
|
|
3431
|
+
// Prefix API calls so they work both standalone and under a demo sub-path
|
|
3432
|
+
// (basePath). router.push / <Link> already apply basePath automatically.
|
|
3433
|
+
const BASE = process.env.NEXT_PUBLIC_BASE_PATH || "";
|
|
3231
3434
|
|
|
3232
3435
|
async function submitEsewaForm(amount: number, orderNumber: string) {
|
|
3233
|
-
const res = await fetch("/api/pay/esewa", {
|
|
3436
|
+
const res = await fetch(BASE + "/api/pay/esewa", {
|
|
3234
3437
|
method: "POST",
|
|
3235
3438
|
headers: { "content-type": "application/json" },
|
|
3236
3439
|
body: JSON.stringify({ amount, orderNumber }),
|
|
@@ -3255,7 +3458,7 @@ export default function Page() {
|
|
|
3255
3458
|
setBusy(true);
|
|
3256
3459
|
setError("");
|
|
3257
3460
|
try {
|
|
3258
|
-
const res = await fetch("/api/checkout", {
|
|
3461
|
+
const res = await fetch(BASE + "/api/checkout", {
|
|
3259
3462
|
method: "POST",
|
|
3260
3463
|
headers: { "content-type": "application/json" },
|
|
3261
3464
|
body: JSON.stringify({ lines, customer: { name, email, address }, paymentMethod: method }),
|
|
@@ -3269,7 +3472,7 @@ export default function Page() {
|
|
|
3269
3472
|
return;
|
|
3270
3473
|
}
|
|
3271
3474
|
if (method === "khalti") {
|
|
3272
|
-
const pay = await fetch("/api/pay/khalti", {
|
|
3475
|
+
const pay = await fetch(BASE + "/api/pay/khalti", {
|
|
3273
3476
|
method: "POST",
|
|
3274
3477
|
headers: { "content-type": "application/json" },
|
|
3275
3478
|
body: JSON.stringify({ amount: order.total, orderNumber: order.orderNumber, orderName: ${JSON.stringify(`${ctx.template.siteName} order`)}, customer: { name, email } }),
|
|
@@ -3335,7 +3538,7 @@ export default function Page() {
|
|
|
3335
3538
|
<div className="flex justify-between text-muted"><dt>Shipping</dt><dd className="tabular-nums">{q.shipping === 0 ? "Free" : formatMoney(q.shipping)}</dd></div>
|
|
3336
3539
|
<div className="flex justify-between border-t border-hairline pt-2 text-base font-bold"><dt>Total</dt><dd className="tabular-nums">{formatMoney(q.total)}</dd></div>
|
|
3337
3540
|
</dl>
|
|
3338
|
-
<button type="submit" disabled={busy} className="mt-6 block w-full rounded-full gradient-bg px-6 py-3 text-center font-semibold
|
|
3541
|
+
<button type="submit" disabled={busy} className="mt-6 block w-full rounded-full gradient-bg px-6 py-3 text-center font-semibold on-accent transition hover:opacity-90 disabled:opacity-60">
|
|
3339
3542
|
{busy ? "Placing order\\u2026" : "Pay " + formatMoney(q.total)}
|
|
3340
3543
|
</button>
|
|
3341
3544
|
</aside>
|
|
@@ -3357,7 +3560,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ o
|
|
|
3357
3560
|
const mock = sp.mock === "1";
|
|
3358
3561
|
return (
|
|
3359
3562
|
<main className="mx-auto flex min-h-screen max-w-2xl flex-col items-center justify-center px-6 py-24 text-center">
|
|
3360
|
-
<div className="flex h-20 w-20 items-center justify-center rounded-full gradient-bg text-4xl
|
|
3563
|
+
<div className="flex h-20 w-20 items-center justify-center rounded-full gradient-bg text-4xl on-accent">\u2713</div>
|
|
3361
3564
|
<h1 className="mt-8 text-4xl font-bold">Thank you!</h1>
|
|
3362
3565
|
<p className="mt-4 text-lg text-muted">Your order with ${ctx.template.siteName} is confirmed.</p>
|
|
3363
3566
|
{order ? (
|
|
@@ -3365,7 +3568,7 @@ export default async function Page({ searchParams }: { searchParams: Promise<{ o
|
|
|
3365
3568
|
) : null}
|
|
3366
3569
|
{method ? <p className="mt-3 text-sm text-muted">Payment method: {method}</p> : null}
|
|
3367
3570
|
{mock ? <p className="mt-3 max-w-md text-sm text-muted">This Khalti payment ran in mock mode. Set KHALTI_SECRET in your environment to hit the real gateway.</p> : null}
|
|
3368
|
-
<Link href="/shop" className="mt-10 inline-block rounded-full gradient-bg px-6 py-3 font-semibold
|
|
3571
|
+
<Link href="/shop" className="mt-10 inline-block rounded-full gradient-bg px-6 py-3 font-semibold on-accent">Continue shopping</Link>
|
|
3369
3572
|
</main>
|
|
3370
3573
|
);
|
|
3371
3574
|
}
|
|
@@ -3537,7 +3740,7 @@ export function SettingsPanel() {
|
|
|
3537
3740
|
<p className="mt-1 text-sm text-muted">Theme via @lacspace/theme.</p>
|
|
3538
3741
|
<div className="mt-4 inline-flex rounded-full border border-hairline p-1">
|
|
3539
3742
|
{["light", "dark", "system"].map((t) => (
|
|
3540
|
-
<button key={t} type="button" onClick={() => setTheme(t)} className={"rounded-full px-4 py-1.5 text-sm capitalize transition " + (theme === t ? "gradient-bg font-semibold
|
|
3743
|
+
<button key={t} type="button" onClick={() => setTheme(t)} className={"rounded-full px-4 py-1.5 text-sm capitalize transition " + (theme === t ? "gradient-bg font-semibold on-accent" : "text-muted hover:text-fg")}>{t}</button>
|
|
3541
3744
|
))}
|
|
3542
3745
|
</div>
|
|
3543
3746
|
</section>
|
|
@@ -3550,11 +3753,11 @@ var uiKitFiles = () => ({
|
|
|
3550
3753
|
|
|
3551
3754
|
export function Section({ eyebrow, title, lead, children, className = "" }: { eyebrow?: string; title?: string; lead?: string; children?: ReactNode; className?: string }) {
|
|
3552
3755
|
return (
|
|
3553
|
-
<section className={"mx-auto max-w-6xl px-6 py-
|
|
3554
|
-
{eyebrow ? <p className="text-sm font-semibold uppercase tracking-widest text
|
|
3756
|
+
<section className={"mx-auto max-w-6xl px-6 py-20 sm:py-24 " + className}>
|
|
3757
|
+
{eyebrow ? <p className="text-sm font-semibold uppercase tracking-widest gradient-text">{eyebrow}</p> : null}
|
|
3555
3758
|
{title ? <h2 className="mt-3 text-3xl font-bold sm:text-4xl">{title}</h2> : null}
|
|
3556
3759
|
{lead ? <p className="mt-4 max-w-2xl text-lg text-muted">{lead}</p> : null}
|
|
3557
|
-
{children ? <div className="mt-
|
|
3760
|
+
{children ? <div className="mt-12">{children}</div> : null}
|
|
3558
3761
|
</section>
|
|
3559
3762
|
);
|
|
3560
3763
|
}
|
|
@@ -3562,15 +3765,16 @@ export function Section({ eyebrow, title, lead, children, className = "" }: { ey
|
|
|
3562
3765
|
"components/ui/pill.tsx": `import type { ReactNode } from "react";
|
|
3563
3766
|
|
|
3564
3767
|
export function Pill({ children, className = "" }: { children: ReactNode; className?: string }) {
|
|
3565
|
-
return <span className={"inline-flex items-center gap-1 rounded-full
|
|
3768
|
+
return <span className={"glass inline-flex items-center gap-1.5 rounded-full px-3.5 py-1.5 text-xs font-semibold uppercase tracking-widest text-muted " + className}><span aria-hidden className="h-1.5 w-1.5 rounded-full gradient-bg" />{children}</span>;
|
|
3566
3769
|
}
|
|
3567
3770
|
`,
|
|
3568
3771
|
"components/ui/stat-card.tsx": `export function StatCard({ label, value, delta }: { label: string; value: string; delta?: string }) {
|
|
3772
|
+
const down = delta?.trim().startsWith("-");
|
|
3569
3773
|
return (
|
|
3570
|
-
<div className="
|
|
3571
|
-
<div className="text-3xl font-bold tabular-nums">{value}</div>
|
|
3774
|
+
<div className="card p-6">
|
|
3775
|
+
<div className="text-3xl font-bold tabular-nums tracking-tight">{value}</div>
|
|
3572
3776
|
<div className="mt-1 text-sm text-muted">{label}</div>
|
|
3573
|
-
{delta ? <div className="mt-2 text-xs font-
|
|
3777
|
+
{delta ? <div className={"mt-3 inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-semibold " + (down ? "bg-red-500/10 text-red-400" : "bg-emerald-500/10 text-emerald-400")}>{down ? "\u25BC" : "\u25B2"} {delta}</div> : null}
|
|
3574
3778
|
</div>
|
|
3575
3779
|
);
|
|
3576
3780
|
}
|
|
@@ -3579,20 +3783,24 @@ export function Pill({ children, className = "" }: { children: ReactNode; classN
|
|
|
3579
3783
|
|
|
3580
3784
|
export function FeatureCard({ icon, title, desc }: { icon?: ReactNode; title: string; desc: string }) {
|
|
3581
3785
|
return (
|
|
3582
|
-
<div className="
|
|
3583
|
-
{icon ? <div className="mb-
|
|
3584
|
-
<h3 className="font-semibold">{title}</h3>
|
|
3585
|
-
<p className="mt-1 text-sm leading-relaxed text-muted">{desc}</p>
|
|
3786
|
+
<div className="card group p-7">
|
|
3787
|
+
{icon ? <div className="mb-5 inline-flex h-12 w-12 items-center justify-center rounded-2xl gradient-bg text-2xl accent-glow transition group-hover:scale-105">{icon}</div> : null}
|
|
3788
|
+
<h3 className="font-semibold tracking-tight">{title}</h3>
|
|
3789
|
+
<p className="mt-1.5 text-sm leading-relaxed text-muted">{desc}</p>
|
|
3586
3790
|
</div>
|
|
3587
3791
|
);
|
|
3588
3792
|
}
|
|
3589
3793
|
`,
|
|
3590
3794
|
"components/ui/testimonial.tsx": `export function Testimonial({ quote, author, role }: { quote: string; author: string; role?: string }) {
|
|
3795
|
+
const initials = author.split(" ").map((x) => x[0]).join("").slice(0, 2).toUpperCase();
|
|
3591
3796
|
return (
|
|
3592
|
-
<figure className="
|
|
3593
|
-
<div className="text-
|
|
3594
|
-
<blockquote className="mt-
|
|
3595
|
-
<figcaption className="mt-
|
|
3797
|
+
<figure className="card flex h-full flex-col p-7">
|
|
3798
|
+
<div aria-hidden className="text-3xl leading-none gradient-text">“</div>
|
|
3799
|
+
<blockquote className="mt-1 flex-1 text-[15px] font-medium leading-relaxed text-fg/90">{quote}</blockquote>
|
|
3800
|
+
<figcaption className="mt-6 flex items-center gap-3 border-t border-hairline pt-5">
|
|
3801
|
+
<span className="inline-flex h-10 w-10 items-center justify-center rounded-full gradient-bg text-sm font-bold on-accent">{initials}</span>
|
|
3802
|
+
<span className="text-sm"><span className="block font-semibold text-fg">{author}</span>{role ? <span className="block text-muted">{role}</span> : null}</span>
|
|
3803
|
+
</figcaption>
|
|
3596
3804
|
</figure>
|
|
3597
3805
|
);
|
|
3598
3806
|
}
|
|
@@ -3601,10 +3809,10 @@ export function FeatureCard({ icon, title, desc }: { icon?: ReactNode; title: st
|
|
|
3601
3809
|
return (
|
|
3602
3810
|
<ol className="grid gap-6 sm:grid-cols-3">
|
|
3603
3811
|
{items.map((s, i) => (
|
|
3604
|
-
<li key={s.title} className="
|
|
3605
|
-
<div className="mb-
|
|
3606
|
-
<h3 className="font-semibold">{s.title}</h3>
|
|
3607
|
-
<p className="mt-1 text-sm text-muted">{s.desc}</p>
|
|
3812
|
+
<li key={s.title} className="card p-7">
|
|
3813
|
+
<div className="mb-4 inline-flex h-9 w-9 items-center justify-center rounded-xl gradient-bg text-sm font-bold on-accent">{String(i + 1).padStart(2, "0")}</div>
|
|
3814
|
+
<h3 className="font-semibold tracking-tight">{s.title}</h3>
|
|
3815
|
+
<p className="mt-1.5 text-sm leading-relaxed text-muted">{s.desc}</p>
|
|
3608
3816
|
</li>
|
|
3609
3817
|
))}
|
|
3610
3818
|
</ol>
|
|
@@ -3615,13 +3823,14 @@ export function FeatureCard({ icon, title, desc }: { icon?: ReactNode; title: st
|
|
|
3615
3823
|
|
|
3616
3824
|
export function CTABand({ title, subtitle, ctaLabel = "Get started", ctaHref = "/contact" }: { title: string; subtitle?: string; ctaLabel?: string; ctaHref?: string }) {
|
|
3617
3825
|
return (
|
|
3618
|
-
<section className="mx-auto max-w-6xl px-6 py-
|
|
3619
|
-
<div className="relative overflow-hidden rounded-
|
|
3620
|
-
<div aria-hidden className="pointer-events-none absolute -right-16 -top-16 h-
|
|
3621
|
-
<div aria-hidden className="pointer-events-none absolute -bottom-
|
|
3622
|
-
<
|
|
3623
|
-
|
|
3624
|
-
<
|
|
3826
|
+
<section className="mx-auto max-w-6xl px-6 py-24">
|
|
3827
|
+
<div className="relative overflow-hidden rounded-[2rem] border border-hairline bg-surface p-12 text-center shadow-lg sm:p-20">
|
|
3828
|
+
<div aria-hidden className="pointer-events-none absolute -right-16 -top-16 h-64 w-64 rounded-full gradient-bg opacity-30 blur-3xl animate-drift" />
|
|
3829
|
+
<div aria-hidden className="pointer-events-none absolute -bottom-20 -left-16 h-64 w-64 rounded-full gradient-bg opacity-25 blur-3xl animate-drift-slow" />
|
|
3830
|
+
<div aria-hidden className="dot-bg pointer-events-none absolute inset-0 opacity-40" />
|
|
3831
|
+
<h2 className="relative text-3xl font-bold tracking-tight sm:text-5xl">{title}</h2>
|
|
3832
|
+
{subtitle ? <p className="relative mx-auto mt-4 max-w-xl text-lg text-muted">{subtitle}</p> : null}
|
|
3833
|
+
<Link href={ctaHref} className="shimmer relative mt-9 inline-block rounded-full gradient-bg px-8 py-3.5 font-semibold on-accent shadow-lg transition hover:-translate-y-0.5">{ctaLabel}</Link>
|
|
3625
3834
|
</div>
|
|
3626
3835
|
</section>
|
|
3627
3836
|
);
|
|
@@ -3683,20 +3892,21 @@ export function Newsletter({ title = "Stay in the loop", subtitle = "Occasional
|
|
|
3683
3892
|
const err = state && !state.ok ? (state.errors.email ?? state.errors._form) : undefined;
|
|
3684
3893
|
|
|
3685
3894
|
return (
|
|
3686
|
-
<div className="mx-auto max-w-
|
|
3687
|
-
<
|
|
3688
|
-
<
|
|
3895
|
+
<div className="gradient-border relative mx-auto max-w-3xl overflow-hidden rounded-[1.75rem] border border-transparent bg-surface p-8 text-center shadow-lg sm:p-12">
|
|
3896
|
+
<div aria-hidden className="pointer-events-none absolute -right-16 -top-16 h-48 w-48 rounded-full gradient-bg opacity-20 blur-3xl" />
|
|
3897
|
+
<h2 className="relative text-2xl font-bold tracking-tight sm:text-3xl">{title}</h2>
|
|
3898
|
+
<p className="relative mx-auto mt-2 max-w-md text-sm text-muted">{subtitle}</p>
|
|
3689
3899
|
{state?.ok ? (
|
|
3690
|
-
<p className="mt-6 rounded-xl border border-hairline bg-app p-4 text-sm">You’re subscribed \u2014 thanks! \u2705</p>
|
|
3900
|
+
<p className="relative mx-auto mt-6 max-w-md rounded-xl border border-hairline bg-app p-4 text-sm">You’re subscribed \u2014 thanks! \u2705</p>
|
|
3691
3901
|
) : (
|
|
3692
|
-
<form action={action} className="mt-
|
|
3693
|
-
<input name="email" type="email" placeholder="you@example.com" aria-label="Email" className="w-full flex-1 rounded-full border border-hairline bg-app px-5 py-3 outline-none focus:border-[color:var(--accent-to)]" />
|
|
3902
|
+
<form action={action} className="relative mx-auto mt-7 flex max-w-md flex-col gap-3 sm:flex-row">
|
|
3903
|
+
<input name="email" type="email" placeholder="you@example.com" aria-label="Email" className="w-full flex-1 rounded-full border border-hairline bg-app px-5 py-3 outline-none transition focus:border-[color:var(--accent-to)]" />
|
|
3694
3904
|
<input {...honeypotProps("website")} />
|
|
3695
3905
|
<input type="hidden" name="_ts" defaultValue={timestampValue()} />
|
|
3696
|
-
<button disabled={pending} className="rounded-full gradient-bg px-6 py-3 font-semibold
|
|
3906
|
+
<button disabled={pending} className="rounded-full gradient-bg px-6 py-3 font-semibold on-accent transition hover:-translate-y-0.5 disabled:opacity-60">{pending ? "Joining\u2026" : "Subscribe"}</button>
|
|
3697
3907
|
</form>
|
|
3698
3908
|
)}
|
|
3699
|
-
{err ? <p className="mt-3 text-sm text-red-400">{err}</p> : null}
|
|
3909
|
+
{err ? <p className="relative mt-3 text-sm text-red-400">{err}</p> : null}
|
|
3700
3910
|
</div>
|
|
3701
3911
|
);
|
|
3702
3912
|
}
|
|
@@ -3705,7 +3915,7 @@ export function Newsletter({ title = "Stay in the loop", subtitle = "Occasional
|
|
|
3705
3915
|
|
|
3706
3916
|
const tones = {
|
|
3707
3917
|
default: "border-hairline bg-surface text-muted",
|
|
3708
|
-
accent: "border-transparent gradient-bg
|
|
3918
|
+
accent: "border-transparent gradient-bg on-accent",
|
|
3709
3919
|
success: "border-emerald-500/30 bg-emerald-500/10 text-emerald-400",
|
|
3710
3920
|
warning: "border-amber-500/30 bg-amber-500/10 text-amber-400",
|
|
3711
3921
|
danger: "border-red-500/30 bg-red-500/10 text-red-400",
|
|
@@ -3779,7 +3989,7 @@ export function Tabs({ tabs }: { tabs: { label: string; content: ReactNode }[] }
|
|
|
3779
3989
|
<div>
|
|
3780
3990
|
<div className="inline-flex flex-wrap gap-1 rounded-full border border-hairline bg-surface p-1">
|
|
3781
3991
|
{tabs.map((t, idx) => (
|
|
3782
|
-
<button key={t.label} onClick={() => setI(idx)} className={"rounded-full px-4 py-2 text-sm font-medium transition " + (i === idx ? "gradient-bg
|
|
3992
|
+
<button key={t.label} onClick={() => setI(idx)} className={"rounded-full px-4 py-2 text-sm font-medium transition " + (i === idx ? "gradient-bg on-accent" : "text-muted hover:text-fg")}>{t.label}</button>
|
|
3783
3993
|
))}
|
|
3784
3994
|
</div>
|
|
3785
3995
|
<div className="mt-6">{tabs[i]?.content}</div>
|
|
@@ -3808,16 +4018,16 @@ interface Tier { name: string; price: string; period?: string; features: string[
|
|
|
3808
4018
|
|
|
3809
4019
|
export function PricingTable({ tiers }: { tiers: Tier[] }) {
|
|
3810
4020
|
return (
|
|
3811
|
-
<div className="grid gap-6 md:grid-cols-3">
|
|
4021
|
+
<div className="grid items-center gap-6 md:grid-cols-3">
|
|
3812
4022
|
{tiers.map((t) => (
|
|
3813
|
-
<div key={t.name} className={"relative flex flex-col rounded-
|
|
3814
|
-
{t.featured ? <span className="absolute -top-3 left-8 rounded-full gradient-bg px-3 py-1 text-xs font-bold
|
|
3815
|
-
<h3 className="font-semibold">{t.name}</h3>
|
|
3816
|
-
<div className="mt-
|
|
3817
|
-
<ul className="mt-
|
|
3818
|
-
{t.features.map((f) => <li key={f} className="flex gap-2"><span className="gradient-text">\u2713</span>{f}</li>)}
|
|
4023
|
+
<div key={t.name} className={"relative flex flex-col rounded-[1.5rem] border p-8 transition " + (t.featured ? "gradient-border border-transparent bg-surface shadow-lg md:scale-[1.04]" : "border-hairline bg-surface hover:border-[color:var(--accent-to)]")}>
|
|
4024
|
+
{t.featured ? <span className="absolute -top-3 left-8 rounded-full gradient-bg px-3 py-1 text-xs font-bold on-accent shadow-sm">Most popular</span> : null}
|
|
4025
|
+
<h3 className="font-semibold tracking-tight">{t.name}</h3>
|
|
4026
|
+
<div className="mt-4 flex items-end gap-1"><span className="text-5xl font-bold tracking-tight">{t.price}</span>{t.period ? <span className="mb-1.5 text-sm text-muted">/{t.period}</span> : null}</div>
|
|
4027
|
+
<ul className="mt-7 flex-1 space-y-3 text-sm text-muted">
|
|
4028
|
+
{t.features.map((f) => <li key={f} className="flex gap-2.5"><span className="mt-0.5 gradient-text font-bold">\u2713</span>{f}</li>)}
|
|
3819
4029
|
</ul>
|
|
3820
|
-
<Link href={t.ctaHref ?? "/contact"} className={"mt-8 rounded-full px-6 py-3 text-center font-semibold transition " + (t.featured ? "gradient-bg
|
|
4030
|
+
<Link href={t.ctaHref ?? "/contact"} className={"mt-8 rounded-full px-6 py-3 text-center font-semibold transition " + (t.featured ? "shimmer gradient-bg on-accent hover:-translate-y-0.5" : "border border-hairline hover:bg-app")}>{t.ctaLabel ?? "Choose " + t.name}</Link>
|
|
3821
4031
|
</div>
|
|
3822
4032
|
))}
|
|
3823
4033
|
</div>
|
|
@@ -3827,9 +4037,9 @@ export function PricingTable({ tiers }: { tiers: Tier[] }) {
|
|
|
3827
4037
|
"components/ui/logo-cloud.tsx": `export function LogoCloud({ names, label }: { names: string[]; label?: string }) {
|
|
3828
4038
|
return (
|
|
3829
4039
|
<div className="text-center">
|
|
3830
|
-
{label ? <p className="text-
|
|
3831
|
-
<div className="mt-
|
|
3832
|
-
{names.map((n) => <span key={n} className="text-lg font-bold text-muted opacity-
|
|
4040
|
+
{label ? <p className="text-xs font-semibold uppercase tracking-widest text-faint">{label}</p> : null}
|
|
4041
|
+
<div className="mt-7 flex flex-wrap items-center justify-center gap-x-12 gap-y-5">
|
|
4042
|
+
{names.map((n) => <span key={n} className="text-lg font-bold tracking-tight text-muted opacity-60 grayscale transition hover:opacity-100 hover:grayscale-0">{n}</span>)}
|
|
3833
4043
|
</div>
|
|
3834
4044
|
</div>
|
|
3835
4045
|
);
|
|
@@ -3837,11 +4047,11 @@ export function PricingTable({ tiers }: { tiers: Tier[] }) {
|
|
|
3837
4047
|
`,
|
|
3838
4048
|
"components/ui/stat-band.tsx": `export function StatBand({ stats }: { stats: { value: string; label: string }[] }) {
|
|
3839
4049
|
return (
|
|
3840
|
-
<div className="grid gap-
|
|
4050
|
+
<div className="grid gap-px overflow-hidden rounded-[1.5rem] border border-hairline bg-hairline sm:grid-cols-2 lg:grid-cols-4">
|
|
3841
4051
|
{stats.map((s) => (
|
|
3842
|
-
<div key={s.label} className="text-center">
|
|
3843
|
-
<div className="gradient-text text-4xl font-bold tabular-nums">{s.value}</div>
|
|
3844
|
-
<div className="mt-
|
|
4052
|
+
<div key={s.label} className="bg-surface px-6 py-10 text-center transition hover:bg-panel">
|
|
4053
|
+
<div className="gradient-text text-4xl font-bold tabular-nums tracking-tight sm:text-5xl">{s.value}</div>
|
|
4054
|
+
<div className="mt-2 text-sm text-muted">{s.label}</div>
|
|
3845
4055
|
</div>
|
|
3846
4056
|
))}
|
|
3847
4057
|
</div>
|
|
@@ -3852,11 +4062,11 @@ export function PricingTable({ tiers }: { tiers: Tier[] }) {
|
|
|
3852
4062
|
return (
|
|
3853
4063
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
3854
4064
|
{members.map((m) => (
|
|
3855
|
-
<div key={m.name} className="
|
|
3856
|
-
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-
|
|
3857
|
-
<h3 className="mt-4 font-semibold">{m.name}</h3>
|
|
3858
|
-
<p className="gradient-text text-sm">{m.role}</p>
|
|
3859
|
-
{m.bio ? <p className="mt-2 text-sm text-muted">{m.bio}</p> : null}
|
|
4065
|
+
<div key={m.name} className="card p-7 text-center">
|
|
4066
|
+
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl gradient-bg text-xl font-bold on-accent accent-glow">{m.name.split(" ").map((x) => x[0]).join("").slice(0, 2)}</div>
|
|
4067
|
+
<h3 className="mt-4 font-semibold tracking-tight">{m.name}</h3>
|
|
4068
|
+
<p className="gradient-text text-sm font-medium">{m.role}</p>
|
|
4069
|
+
{m.bio ? <p className="mt-2 text-sm leading-relaxed text-muted">{m.bio}</p> : null}
|
|
3860
4070
|
</div>
|
|
3861
4071
|
))}
|
|
3862
4072
|
</div>
|
|
@@ -3932,7 +4142,7 @@ export function Breadcrumbs({ items }: { items: { label: string; href?: string }
|
|
|
3932
4142
|
`,
|
|
3933
4143
|
"components/ui/avatar.tsx": `export function Avatar({ name, size = 40 }: { name: string; size?: number }) {
|
|
3934
4144
|
const initials = name.split(" ").map((x) => x[0]).join("").slice(0, 2).toUpperCase();
|
|
3935
|
-
return <span className="inline-flex items-center justify-center rounded-full gradient-bg font-bold
|
|
4145
|
+
return <span className="inline-flex items-center justify-center rounded-full gradient-bg font-bold on-accent" style={{ width: size, height: size, fontSize: size * 0.4 }}>{initials}</span>;
|
|
3936
4146
|
}
|
|
3937
4147
|
|
|
3938
4148
|
export function AvatarGroup({ names }: { names: string[] }) {
|
|
@@ -4153,10 +4363,15 @@ function creativeData(ctx) {
|
|
|
4153
4363
|
}
|
|
4154
4364
|
var aurora = () => `export function Aurora() {
|
|
4155
4365
|
return (
|
|
4156
|
-
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[
|
|
4157
|
-
|
|
4158
|
-
<div className="absolute -
|
|
4159
|
-
<div className="absolute
|
|
4366
|
+
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[820px] overflow-hidden">
|
|
4367
|
+
{/* soft gradient-mesh \u2014 blurred orbs keyed to the accent, drifting slowly */}
|
|
4368
|
+
<div className="absolute -left-32 -top-24 h-[32rem] w-[32rem] rounded-full opacity-25 blur-[90px] animate-drift" style={{ background: "radial-gradient(circle at 50% 50%, var(--accent-from), transparent 68%)" }} />
|
|
4369
|
+
<div className="absolute -right-24 top-24 h-[30rem] w-[30rem] rounded-full opacity-20 blur-[90px] animate-drift-slow" style={{ background: "radial-gradient(circle at 50% 50%, var(--accent-to), transparent 68%)" }} />
|
|
4370
|
+
<div className="absolute left-1/3 top-64 h-96 w-96 rounded-full opacity-[0.14] blur-[80px] animate-floaty" style={{ background: "radial-gradient(circle at 50% 50%, var(--accent-to), transparent 70%)" }} />
|
|
4371
|
+
{/* faint dotted texture, fading out toward the fold */}
|
|
4372
|
+
<div className="dot-bg absolute inset-0 opacity-60" />
|
|
4373
|
+
{/* seam that melts the aurora into the page */}
|
|
4374
|
+
<div className="absolute inset-x-0 bottom-0 h-40 bg-gradient-to-b from-transparent to-[var(--bg)]" />
|
|
4160
4375
|
</div>
|
|
4161
4376
|
);
|
|
4162
4377
|
}
|
|
@@ -4167,22 +4382,25 @@ var heroArt = (ctx) => {
|
|
|
4167
4382
|
return `export function HeroArt() {
|
|
4168
4383
|
return (
|
|
4169
4384
|
<div className="relative mx-auto aspect-square w-full max-w-sm select-none">
|
|
4385
|
+
{/* ambient accent glow behind the whole piece */}
|
|
4386
|
+
<div aria-hidden className="absolute inset-6 rounded-full opacity-30 blur-3xl gradient-bg animate-drift-slow" />
|
|
4170
4387
|
{/* orbiting rings */}
|
|
4171
4388
|
<div className="absolute inset-0 rounded-full border border-hairline animate-spin-slow" />
|
|
4172
|
-
<div className="absolute inset-10 rounded-full border border-hairline animate-spin-slow" style={{ animationDirection: "reverse" }} />
|
|
4173
|
-
|
|
4389
|
+
<div className="absolute inset-10 rounded-full border border-dashed border-hairline animate-spin-slow" style={{ animationDirection: "reverse" }} />
|
|
4390
|
+
{/* orbit node */}
|
|
4391
|
+
<div className="absolute left-1/2 top-0 h-2.5 w-2.5 -translate-x-1/2 rounded-full gradient-bg animate-spin-slow" style={{ transformOrigin: "50% 190px" }} />
|
|
4174
4392
|
{/* center badge with the platform initials */}
|
|
4175
|
-
<div className="absolute left-1/2 top-1/2 flex h-
|
|
4393
|
+
<div className="absolute left-1/2 top-1/2 flex h-32 w-32 -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-[1.75rem] gradient-bg text-4xl font-black on-accent shadow-lg accent-glow animate-floaty" style={{ animationDuration: "7s" }}>
|
|
4176
4394
|
${JSON.stringify(initials)}
|
|
4177
4395
|
</div>
|
|
4178
|
-
{/* floating
|
|
4179
|
-
<div className="absolute left-0 top-10 flex items-center gap-2 rounded-
|
|
4180
|
-
<span>${d.emoji}</span> ${JSON.stringify(d.chips[0])}
|
|
4396
|
+
{/* floating glass chips */}
|
|
4397
|
+
<div className="glass absolute left-0 top-10 flex items-center gap-2 rounded-2xl px-3.5 py-2 text-xs font-semibold shadow-lg animate-floaty">
|
|
4398
|
+
<span className="text-sm">${d.emoji}</span> ${JSON.stringify(d.chips[0])}
|
|
4181
4399
|
</div>
|
|
4182
|
-
<div className="absolute right-0 top-28
|
|
4183
|
-
${JSON.stringify(d.chips[1])}
|
|
4400
|
+
<div className="glass absolute right-0 top-28 flex items-center gap-1.5 rounded-2xl px-3.5 py-2 text-xs font-semibold shadow-lg animate-floaty-slow">
|
|
4401
|
+
<span className="h-1.5 w-1.5 rounded-full gradient-bg" /> ${JSON.stringify(d.chips[1])}
|
|
4184
4402
|
</div>
|
|
4185
|
-
<div className="absolute bottom-8 left-6 rounded-
|
|
4403
|
+
<div className="glass absolute bottom-8 left-6 rounded-2xl px-3.5 py-2 text-xs font-semibold shadow-lg animate-floaty" style={{ animationDelay: "1.2s" }}>
|
|
4186
4404
|
${JSON.stringify(d.chips[2])}
|
|
4187
4405
|
</div>
|
|
4188
4406
|
</div>
|
|
@@ -4264,30 +4482,29 @@ var extraHomeSections = (ctx) => {
|
|
|
4264
4482
|
const r = richContent(ctx);
|
|
4265
4483
|
const tst = r.testimonials.map((t) => `<Testimonial quote={${JSON.stringify(t.quote)}} author={${JSON.stringify(t.author)}} role={${JSON.stringify(t.role ?? "")}} />`).join("\n ");
|
|
4266
4484
|
return `<section className="mx-auto max-w-6xl px-6 py-16">
|
|
4267
|
-
<
|
|
4268
|
-
<
|
|
4485
|
+
<div className="mb-10 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">By the numbers</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Built to perform</h2></div>
|
|
4486
|
+
<StatBand stats={${JSON.stringify(r.stats)}} />
|
|
4269
4487
|
</section>
|
|
4270
4488
|
<section className="mx-auto max-w-6xl px-6 py-16">
|
|
4271
|
-
<
|
|
4272
|
-
<
|
|
4273
|
-
<div className="mt-10"><Steps items={${JSON.stringify(r.process)}} /></div>
|
|
4489
|
+
<div className="mb-12 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">How it works</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">A simple, proven path</h2><p className="mx-auto mt-3 max-w-xl text-muted">From first hello to launch day \u2014 no surprises.</p></div>
|
|
4490
|
+
<Steps items={${JSON.stringify(r.process)}} />
|
|
4274
4491
|
</section>
|
|
4275
4492
|
<section className="mx-auto max-w-6xl px-6 py-16">
|
|
4276
|
-
<
|
|
4277
|
-
<div className="
|
|
4493
|
+
<div className="mb-12 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">Loved by teams</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Don't just take our word for it</h2></div>
|
|
4494
|
+
<div className="grid items-stretch gap-6 md:grid-cols-3">
|
|
4278
4495
|
${tst}
|
|
4279
4496
|
</div>
|
|
4280
4497
|
</section>`;
|
|
4281
4498
|
};
|
|
4282
4499
|
var showcaseSection = (ctx) => {
|
|
4283
4500
|
const d = creativeData(ctx);
|
|
4284
|
-
const bullets = d.bullets.map((b) => `<li className="flex items-center gap-
|
|
4285
|
-
return `<section className="mx-auto grid max-w-6xl items-center gap-12 px-6 py-
|
|
4501
|
+
const bullets = d.bullets.map((b) => `<li className="flex items-center gap-3 text-muted"><span className="inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full gradient-bg text-[11px] on-accent">\u2713</span> ${b}</li>`).join("\n ");
|
|
4502
|
+
return `<section className="mx-auto grid max-w-6xl items-center gap-12 px-6 py-24 md:grid-cols-2">
|
|
4286
4503
|
<div>
|
|
4287
|
-
<p className="text-sm font-semibold uppercase tracking-widest text
|
|
4504
|
+
<p className="text-sm font-semibold uppercase tracking-widest gradient-text">${ctx.template.siteName}</p>
|
|
4288
4505
|
<h2 className="mt-3 text-3xl font-bold sm:text-4xl">${d.showcaseTitle}</h2>
|
|
4289
4506
|
<p className="mt-4 max-w-md text-lg text-muted">${d.showcaseLead}</p>
|
|
4290
|
-
<ul className="mt-
|
|
4507
|
+
<ul className="mt-7 space-y-3.5 text-sm font-medium">
|
|
4291
4508
|
${bullets}
|
|
4292
4509
|
</ul>
|
|
4293
4510
|
</div>
|
|
@@ -4296,10 +4513,10 @@ var showcaseSection = (ctx) => {
|
|
|
4296
4513
|
};
|
|
4297
4514
|
var marqueeSection = (ctx) => {
|
|
4298
4515
|
const d = creativeData(ctx);
|
|
4299
|
-
const chip = (t) => `<span className="rounded-full border border-hairline bg-surface px-5 py-2 text-sm font-
|
|
4516
|
+
const chip = (t) => `<span className="whitespace-nowrap rounded-full border border-hairline bg-surface px-5 py-2 text-sm font-semibold text-muted">${t}</span>`;
|
|
4300
4517
|
const track = d.marquee.map(chip).join("\n ");
|
|
4301
|
-
return `<section className="
|
|
4302
|
-
<p className="mb-
|
|
4518
|
+
return `<section className="py-14">
|
|
4519
|
+
<p className="mb-8 text-center text-xs font-semibold uppercase tracking-widest text-faint">${d.marqueeLabel}</p>
|
|
4303
4520
|
<div className="marquee-mask overflow-hidden">
|
|
4304
4521
|
<div className="marquee-track gap-4">
|
|
4305
4522
|
${track}
|
|
@@ -4310,16 +4527,16 @@ var marqueeSection = (ctx) => {
|
|
|
4310
4527
|
};
|
|
4311
4528
|
var faqSection = (ctx) => {
|
|
4312
4529
|
const d = creativeData(ctx);
|
|
4313
|
-
const items = d.faq.map((f) => `<details className="group rounded-2xl border border-hairline bg-surface p-5">
|
|
4314
|
-
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 font-
|
|
4530
|
+
const items = d.faq.map((f) => `<details className="group rounded-2xl border border-hairline bg-surface p-5 transition hover:border-[color:var(--accent-to)]">
|
|
4531
|
+
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 font-semibold">
|
|
4315
4532
|
${f.q}
|
|
4316
|
-
<span className="text-muted transition group-open:rotate-45">+</span>
|
|
4533
|
+
<span className="text-lg text-muted transition group-open:rotate-45">+</span>
|
|
4317
4534
|
</summary>
|
|
4318
4535
|
<p className="mt-3 text-sm leading-relaxed text-muted">${f.a}</p>
|
|
4319
4536
|
</details>`).join("\n ");
|
|
4320
|
-
return `<section className="mx-auto max-w-3xl px-6 py-
|
|
4321
|
-
<
|
|
4322
|
-
<div className="
|
|
4537
|
+
return `<section className="mx-auto max-w-3xl px-6 py-24">
|
|
4538
|
+
<div className="mb-10 text-center"><p className="text-sm font-semibold uppercase tracking-widest gradient-text">FAQ</p><h2 className="mt-3 text-3xl font-bold sm:text-4xl">Frequently asked</h2></div>
|
|
4539
|
+
<div className="space-y-3">
|
|
4323
4540
|
${items}
|
|
4324
4541
|
</div>
|
|
4325
4542
|
</section>`;
|
|
@@ -4454,13 +4671,13 @@ import { Pill } from "@/components/ui";
|
|
|
4454
4671
|
|
|
4455
4672
|
export function HeroSection() {
|
|
4456
4673
|
return (
|
|
4457
|
-
<section className="mx-auto max-w-4xl px-6 py-
|
|
4674
|
+
<section className="mx-auto max-w-4xl px-6 py-32 text-center">
|
|
4458
4675
|
<Pill>New</Pill>
|
|
4459
|
-
<h1 className="
|
|
4460
|
-
<p className="mx-auto mt-
|
|
4461
|
-
<div className="mt-
|
|
4462
|
-
<Link href="/contact" className="rounded-full gradient-bg px-7 py-3 font-semibold
|
|
4463
|
-
<Link href="/about" className="rounded-full border border-hairline px-7 py-3 font-semibold transition hover:bg-surface">Learn more</Link>
|
|
4676
|
+
<h1 className="text-display mt-6">Build something <span className="gradient-text">people love</span></h1>
|
|
4677
|
+
<p className="lead mx-auto mt-6 max-w-xl">A confident headline and one sentence that sells the outcome \u2014 edit me.</p>
|
|
4678
|
+
<div className="mt-9 flex flex-wrap justify-center gap-3">
|
|
4679
|
+
<Link href="/contact" className="shimmer rounded-full gradient-bg px-7 py-3.5 font-semibold on-accent transition hover:-translate-y-0.5">Get started</Link>
|
|
4680
|
+
<Link href="/about" className="rounded-full border border-hairline px-7 py-3.5 font-semibold transition hover:bg-surface">Learn more</Link>
|
|
4464
4681
|
</div>
|
|
4465
4682
|
</section>
|
|
4466
4683
|
);
|
|
@@ -4622,6 +4839,53 @@ export function BentoSection() {
|
|
|
4622
4839
|
</Section>
|
|
4623
4840
|
);
|
|
4624
4841
|
}
|
|
4842
|
+
`,
|
|
4843
|
+
steps: `import { Section, Steps } from "@/components/ui";
|
|
4844
|
+
|
|
4845
|
+
export function StepsSection() {
|
|
4846
|
+
return (
|
|
4847
|
+
<Section eyebrow="How it works" title="Up and running in three steps">
|
|
4848
|
+
<Steps items={[
|
|
4849
|
+
{ title: "Create your account", desc: "Sign up free in seconds \u2014 no credit card required." },
|
|
4850
|
+
{ title: "Connect your data", desc: "Import in one click or use the API. Your call." },
|
|
4851
|
+
{ title: "Ship something great", desc: "Go live and watch it work, from day one." },
|
|
4852
|
+
]} />
|
|
4853
|
+
</Section>
|
|
4854
|
+
);
|
|
4855
|
+
}
|
|
4856
|
+
`,
|
|
4857
|
+
"feature-split": `import { Section, FeatureSplit } from "@/components/ui";
|
|
4858
|
+
|
|
4859
|
+
export function FeatureSplitSection() {
|
|
4860
|
+
return (
|
|
4861
|
+
<Section eyebrow="Why teams switch" title="Built for the way you actually work">
|
|
4862
|
+
<FeatureSplit
|
|
4863
|
+
eyebrow="Focus"
|
|
4864
|
+
title="Less busywork, more momentum"
|
|
4865
|
+
desc="Everything in one place, so your team spends time on the work that matters \u2014 not on wiring tools together."
|
|
4866
|
+
bullets={["Set up in minutes, not weeks", "Real-time, always in sync", "Scales from one to a thousand"]}
|
|
4867
|
+
media={<div className="grid h-52 place-items-center text-7xl">\u{1F680}</div>}
|
|
4868
|
+
/>
|
|
4869
|
+
</Section>
|
|
4870
|
+
);
|
|
4871
|
+
}
|
|
4872
|
+
`,
|
|
4873
|
+
banner: `import Link from "next/link";
|
|
4874
|
+
|
|
4875
|
+
export function BannerSection() {
|
|
4876
|
+
return (
|
|
4877
|
+
<section className="mx-auto max-w-6xl px-6 py-12">
|
|
4878
|
+
<div className="relative flex flex-col items-center gap-6 overflow-hidden rounded-[1.75rem] gradient-bg px-8 py-10 text-center on-accent shadow-lg sm:flex-row sm:justify-between sm:text-left">
|
|
4879
|
+
<div aria-hidden className="dot-bg pointer-events-none absolute inset-0 opacity-20" />
|
|
4880
|
+
<div className="relative">
|
|
4881
|
+
<h2 className="text-2xl font-bold tracking-tight sm:text-3xl">Ready to see it in action?</h2>
|
|
4882
|
+
<p className="mt-1.5 text-sm opacity-80">Start free today \u2014 it takes less than a minute.</p>
|
|
4883
|
+
</div>
|
|
4884
|
+
<Link href="/contact" className="relative shrink-0 rounded-full bg-[var(--bg)] px-7 py-3 font-semibold text-fg transition hover:-translate-y-0.5">Get started</Link>
|
|
4885
|
+
</div>
|
|
4886
|
+
</section>
|
|
4887
|
+
);
|
|
4888
|
+
}
|
|
4625
4889
|
`
|
|
4626
4890
|
};
|
|
4627
4891
|
function ensureUiKit(root) {
|