create-lacspace-app 1.6.0 → 1.7.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/dist/index.js +179 -93
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,6 +43,9 @@ var pkgJson = (ctx) => JSON.stringify({
|
|
|
43
43
|
"@lacspace/ui": "^1.0.0",
|
|
44
44
|
"@lacspace/form": "^1.0.0",
|
|
45
45
|
"@lacspace/validate": "^1.0.0",
|
|
46
|
+
// React Kit: dark/light theming (no-flash) + essential hooks.
|
|
47
|
+
"@lacspace/theme": "^1.0.1",
|
|
48
|
+
"@lacspace/hooks": "^1.0.0",
|
|
46
49
|
// The blog & docs templates render Markdown with @lacspace/markdown.
|
|
47
50
|
...ctx.template.key === "blog" || ctx.template.key === "docs" ? { "@lacspace/markdown": "^1.0.0" } : {}
|
|
48
51
|
},
|
|
@@ -103,25 +106,57 @@ out
|
|
|
103
106
|
`;
|
|
104
107
|
var globalsCss = (ctx) => `@import "tailwindcss";
|
|
105
108
|
|
|
109
|
+
/* Class-based dark mode \u2014 toggled by @lacspace/theme's <ThemeProvider>. */
|
|
110
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
111
|
+
|
|
112
|
+
/* Semantic color tokens \u2192 Tailwind utilities (text-fg, text-muted, bg-surface,
|
|
113
|
+
border-hairline, \u2026). They resolve to the CSS vars below, which swap per theme. */
|
|
114
|
+
@theme inline {
|
|
115
|
+
--color-fg: var(--fg);
|
|
116
|
+
--color-muted: var(--muted);
|
|
117
|
+
--color-faint: var(--faint);
|
|
118
|
+
--color-surface: var(--surface);
|
|
119
|
+
--color-panel: var(--panel);
|
|
120
|
+
--color-hairline: var(--hairline);
|
|
121
|
+
}
|
|
122
|
+
|
|
106
123
|
:root {
|
|
107
124
|
--accent-from: ${ctx.template.accent[0]};
|
|
108
125
|
--accent-to: ${ctx.template.accent[1]};
|
|
126
|
+
/* Light theme */
|
|
127
|
+
--bg: #ffffff;
|
|
128
|
+
--fg: #0a0a0f;
|
|
129
|
+
--muted: #3f3f46;
|
|
130
|
+
--faint: #71717a;
|
|
131
|
+
--surface: rgb(0 0 0 / 0.03);
|
|
132
|
+
--panel: rgb(0 0 0 / 0.06);
|
|
133
|
+
--hairline: rgb(0 0 0 / 0.10);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.dark {
|
|
137
|
+
/* Dark theme (default) */
|
|
109
138
|
--bg: #0a0a0f;
|
|
110
|
-
--fg: #
|
|
139
|
+
--fg: #f5f5f7;
|
|
140
|
+
--muted: rgb(255 255 255 / 0.62);
|
|
141
|
+
--faint: rgb(255 255 255 / 0.42);
|
|
142
|
+
--surface: rgb(255 255 255 / 0.04);
|
|
143
|
+
--panel: rgb(255 255 255 / 0.08);
|
|
144
|
+
--hairline: rgb(255 255 255 / 0.10);
|
|
111
145
|
}
|
|
112
146
|
|
|
113
|
-
* { border-color:
|
|
147
|
+
* { border-color: var(--hairline); }
|
|
114
148
|
html { scroll-behavior: smooth; }
|
|
115
149
|
body {
|
|
116
150
|
background: var(--bg);
|
|
117
151
|
color: var(--fg);
|
|
118
152
|
-webkit-font-smoothing: antialiased;
|
|
119
153
|
text-rendering: optimizeLegibility;
|
|
154
|
+
transition: background-color 0.3s ease, color 0.3s ease;
|
|
120
155
|
}
|
|
121
156
|
|
|
122
|
-
::selection { background: var(--accent-to); color: #
|
|
157
|
+
::selection { background: var(--accent-to); color: #fff; }
|
|
123
158
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
124
|
-
::-webkit-scrollbar-thumb { background:
|
|
159
|
+
::-webkit-scrollbar-thumb { background: var(--panel); border-radius: 8px; }
|
|
125
160
|
:focus-visible { outline: 2px solid var(--accent-to); outline-offset: 2px; border-radius: 4px; }
|
|
126
161
|
|
|
127
162
|
.gradient-text {
|
|
@@ -168,6 +203,7 @@ export const site = defineSite({
|
|
|
168
203
|
`;
|
|
169
204
|
var layout = (ctx) => `import type { Metadata } from "next";
|
|
170
205
|
import { Inter } from "next/font/google";
|
|
206
|
+
import { ThemeProvider } from "@lacspace/theme";
|
|
171
207
|
import { site } from "@/lib/site";
|
|
172
208
|
import { CommandMenu } from "@/components/command-menu";
|
|
173
209
|
import "./globals.css";
|
|
@@ -178,11 +214,14 @@ export const metadata: Metadata = site.meta({ title: ${JSON.stringify(ctx.templa
|
|
|
178
214
|
|
|
179
215
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
180
216
|
return (
|
|
181
|
-
<html lang="en" className={inter.className}>
|
|
217
|
+
<html lang="en" className={inter.className} suppressHydrationWarning>
|
|
182
218
|
<body className="antialiased">
|
|
183
|
-
{/* \u2728
|
|
184
|
-
<
|
|
185
|
-
|
|
219
|
+
{/* \u2728 Dark / light / system theming with a built-in no-flash script \u2014 @lacspace/theme */}
|
|
220
|
+
<ThemeProvider defaultTheme="dark">
|
|
221
|
+
{/* \u2728 Press \u2318K / Ctrl-K anywhere \u2014 powered by @lacspace/ui */}
|
|
222
|
+
<CommandMenu />
|
|
223
|
+
{children}
|
|
224
|
+
</ThemeProvider>
|
|
186
225
|
<script
|
|
187
226
|
type="application/ld+json"
|
|
188
227
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(site.rootJsonLd()) }}
|
|
@@ -226,7 +265,7 @@ export default function NotFound() {
|
|
|
226
265
|
return (
|
|
227
266
|
<main className="flex min-h-screen flex-col items-center justify-center gap-6 px-6 text-center">
|
|
228
267
|
<div className="text-8xl font-black gradient-text">404</div>
|
|
229
|
-
<p className="text-lg text-
|
|
268
|
+
<p className="text-lg text-muted">This page wandered off.</p>
|
|
230
269
|
<Link href="/" className="gradient-bg rounded-full px-6 py-3 font-semibold text-black">Back home</Link>
|
|
231
270
|
</main>
|
|
232
271
|
);
|
|
@@ -339,21 +378,25 @@ Open [http://localhost:3000](http://localhost:3000). Edit \`app/page.tsx\` and \
|
|
|
339
378
|
|
|
340
379
|
Built with [Lacspace](https://lacspace.com/packages).
|
|
341
380
|
`;
|
|
342
|
-
var NAV = (name, links) => `<header className="sticky top-0 z-40 backdrop-blur border-b border-
|
|
381
|
+
var NAV = (name, links) => `<header className="sticky top-0 z-40 backdrop-blur border-b border-hairline">
|
|
343
382
|
<nav className="mx-auto flex max-w-6xl items-center justify-between px-6 py-4">
|
|
344
383
|
<span className="text-lg font-black gradient-text">${name}</span>
|
|
345
|
-
<div className="
|
|
346
|
-
|
|
384
|
+
<div className="flex items-center gap-6">
|
|
385
|
+
<div className="hidden gap-8 text-sm text-muted sm:flex">
|
|
386
|
+
${links.map((l) => `<a href="#${l.toLowerCase()}" className="hover:text-fg">${l}</a>`).join("\n ")}
|
|
387
|
+
</div>
|
|
388
|
+
<ThemeToggle />
|
|
347
389
|
</div>
|
|
348
390
|
</nav>
|
|
349
391
|
</header>`;
|
|
350
|
-
var FOOTER = (name) => `<footer className="border-t border-
|
|
392
|
+
var FOOTER = (name) => `<footer className="border-t border-hairline py-10 text-center text-sm text-faint">
|
|
351
393
|
\xA9 {new Date().getFullYear()} ${name}. Built with{" "}
|
|
352
|
-
<a href="https://lacspace.com/packages" className="text-
|
|
394
|
+
<a href="https://lacspace.com/packages" className="text-muted hover:text-fg">Lacspace</a>.
|
|
353
395
|
</footer>`;
|
|
354
396
|
function homePage(ctx) {
|
|
355
397
|
const n = ctx.template.siteName;
|
|
356
398
|
const shell = (inner) => `import { site } from "@/lib/site";
|
|
399
|
+
import { ThemeToggle } from "@/components/theme-toggle";
|
|
357
400
|
|
|
358
401
|
// \u2728 Self-canonical home page \u2014 one line, full SEO (title, canonical, OG, Twitter).
|
|
359
402
|
export const metadata = site.meta({ title: ${JSON.stringify(n)}, path: "/" });
|
|
@@ -369,22 +412,22 @@ export default function Home() {
|
|
|
369
412
|
if (ctx.template.key === "personal") {
|
|
370
413
|
return shell(`${NAV(n, ["Work", "About", "Contact"])}
|
|
371
414
|
<section className="mx-auto max-w-3xl px-6 py-28 text-center">
|
|
372
|
-
<p className="mb-4 text-sm font-semibold uppercase tracking-widest text-
|
|
415
|
+
<p className="mb-4 text-sm font-semibold uppercase tracking-widest text-faint">Portfolio</p>
|
|
373
416
|
<h1 className="text-5xl font-black leading-tight sm:text-7xl">Hi, I'm <span className="gradient-text">${n}</span>.</h1>
|
|
374
|
-
<p className="mx-auto mt-6 max-w-xl text-lg text-
|
|
417
|
+
<p className="mx-auto mt-6 max-w-xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
375
418
|
<div className="mt-10 flex justify-center gap-4">
|
|
376
419
|
<a href="#work" className="gradient-bg rounded-full px-6 py-3 font-semibold text-black">View my work</a>
|
|
377
|
-
<a href="#contact" className="rounded-full border border-
|
|
420
|
+
<a href="#contact" className="rounded-full border border-hairline px-6 py-3 font-semibold hover:bg-surface">Get in touch</a>
|
|
378
421
|
</div>
|
|
379
422
|
</section>
|
|
380
423
|
<section id="work" className="mx-auto max-w-6xl px-6 py-16">
|
|
381
424
|
<h2 className="mb-10 text-3xl font-bold">Selected work</h2>
|
|
382
425
|
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
383
426
|
{[1, 2, 3, 4, 5, 6].map((i) => (
|
|
384
|
-
<div key={i} className="group rounded-2xl border border-
|
|
427
|
+
<div key={i} className="group rounded-2xl border border-hairline bg-surface p-6 transition hover:-translate-y-1 hover:border-hairline">
|
|
385
428
|
<div className="mb-4 h-32 rounded-xl gradient-bg opacity-80" />
|
|
386
429
|
<h3 className="font-semibold">Project {i}</h3>
|
|
387
|
-
<p className="mt-1 text-sm text-
|
|
430
|
+
<p className="mt-1 text-sm text-muted">A short description of what you built and the impact it had.</p>
|
|
388
431
|
</div>
|
|
389
432
|
))}
|
|
390
433
|
</div>
|
|
@@ -395,7 +438,7 @@ export default function Home() {
|
|
|
395
438
|
return shell(`${NAV(n, ["Services", "Work", "Contact"])}
|
|
396
439
|
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
397
440
|
<h1 className="text-5xl font-black leading-tight sm:text-6xl">We build products <span className="gradient-text">that grow businesses</span>.</h1>
|
|
398
|
-
<p className="mx-auto mt-6 max-w-2xl text-lg text-
|
|
441
|
+
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
399
442
|
<a href="#contact" className="mt-10 inline-block gradient-bg rounded-full px-8 py-3 font-semibold text-black">Start a project</a>
|
|
400
443
|
</section>
|
|
401
444
|
<section id="services" className="mx-auto max-w-6xl px-6 py-16">
|
|
@@ -406,17 +449,17 @@ export default function Home() {
|
|
|
406
449
|
{ t: "Design", d: "Brand and product design that people remember." },
|
|
407
450
|
{ t: "Engineering", d: "Fast, reliable software built to last." },
|
|
408
451
|
].map((s) => (
|
|
409
|
-
<div key={s.t} className="rounded-2xl border border-
|
|
452
|
+
<div key={s.t} className="rounded-2xl border border-hairline bg-surface p-8">
|
|
410
453
|
<h3 className="text-xl font-bold gradient-text">{s.t}</h3>
|
|
411
|
-
<p className="mt-2 text-
|
|
454
|
+
<p className="mt-2 text-muted">{s.d}</p>
|
|
412
455
|
</div>
|
|
413
456
|
))}
|
|
414
457
|
</div>
|
|
415
458
|
</section>
|
|
416
459
|
<section id="contact" className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
417
460
|
<h2 className="text-3xl font-bold">Let's work together</h2>
|
|
418
|
-
<p className="mt-3 text-
|
|
419
|
-
<a href="mailto:hello@example.com" className="mt-8 inline-block rounded-full border border-
|
|
461
|
+
<p className="mt-3 text-muted">Tell us about your project and we'll get back within a day.</p>
|
|
462
|
+
<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>
|
|
420
463
|
</section>
|
|
421
464
|
${FOOTER(n)}`);
|
|
422
465
|
}
|
|
@@ -424,7 +467,7 @@ export default function Home() {
|
|
|
424
467
|
return shell(`${NAV(n, ["Shop", "About", "Cart"])}
|
|
425
468
|
<section className="mx-auto max-w-5xl px-6 py-24 text-center">
|
|
426
469
|
<h1 className="text-5xl font-black leading-tight sm:text-6xl"><span className="gradient-text">${n}</span></h1>
|
|
427
|
-
<p className="mx-auto mt-6 max-w-xl text-lg text-
|
|
470
|
+
<p className="mx-auto mt-6 max-w-xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
428
471
|
<a href="#shop" className="mt-10 inline-block gradient-bg rounded-full px-8 py-3 font-semibold text-black">Shop the collection</a>
|
|
429
472
|
</section>
|
|
430
473
|
<section id="shop" className="mx-auto max-w-6xl px-6 py-16">
|
|
@@ -434,13 +477,13 @@ export default function Home() {
|
|
|
434
477
|
{ n: "Aurora Lamp", p: "$89" }, { n: "Terra Mug", p: "$24" },
|
|
435
478
|
{ n: "Linen Throw", p: "$65" }, { n: "Oak Stand", p: "$120" },
|
|
436
479
|
].map((prod) => (
|
|
437
|
-
<div key={prod.n} className="group rounded-2xl border border-
|
|
480
|
+
<div key={prod.n} className="group rounded-2xl border border-hairline bg-surface p-4 transition hover:-translate-y-1">
|
|
438
481
|
<div className="mb-4 aspect-square rounded-xl gradient-bg opacity-80" />
|
|
439
482
|
<div className="flex items-center justify-between">
|
|
440
483
|
<h3 className="font-semibold">{prod.n}</h3>
|
|
441
|
-
<span className="text-
|
|
484
|
+
<span className="text-muted">{prod.p}</span>
|
|
442
485
|
</div>
|
|
443
|
-
<button className="mt-3 w-full rounded-lg border border-
|
|
486
|
+
<button className="mt-3 w-full rounded-lg border border-hairline py-2 text-sm font-medium hover:bg-surface">Add to cart</button>
|
|
444
487
|
</div>
|
|
445
488
|
))}
|
|
446
489
|
</div>
|
|
@@ -450,16 +493,16 @@ export default function Home() {
|
|
|
450
493
|
if (ctx.template.key === "blog") {
|
|
451
494
|
return shell(`${NAV(n, ["Latest", "Topics", "About"])}
|
|
452
495
|
<section className="mx-auto max-w-3xl px-6 py-24">
|
|
453
|
-
<p className="mb-3 text-sm font-semibold uppercase tracking-widest text-
|
|
496
|
+
<p className="mb-3 text-sm font-semibold uppercase tracking-widest text-faint">The Journal</p>
|
|
454
497
|
<h1 className="text-5xl font-black leading-tight sm:text-6xl gradient-text">${n}</h1>
|
|
455
|
-
<p className="mt-6 text-lg text-
|
|
498
|
+
<p className="mt-6 text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
456
499
|
</section>
|
|
457
500
|
<section id="latest" className="mx-auto max-w-5xl px-6 pb-8">
|
|
458
|
-
<a href="#" className="group block rounded-3xl border border-
|
|
501
|
+
<a href="#" className="group block rounded-3xl border border-hairline bg-surface p-8 transition hover:border-hairline">
|
|
459
502
|
<div className="mb-6 h-56 rounded-2xl gradient-bg opacity-80" />
|
|
460
|
-
<span className="text-xs font-semibold uppercase tracking-wider text-
|
|
503
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-faint">Featured</span>
|
|
461
504
|
<h2 className="mt-2 text-3xl font-bold group-hover:opacity-90">The one thing every product needs before launch</h2>
|
|
462
|
-
<p className="mt-2 text-
|
|
505
|
+
<p className="mt-2 text-muted">A short, punchy dek that pulls the reader into the piece and makes them want more.</p>
|
|
463
506
|
</a>
|
|
464
507
|
</section>
|
|
465
508
|
<section className="mx-auto max-w-5xl px-6 py-12">
|
|
@@ -467,9 +510,9 @@ export default function Home() {
|
|
|
467
510
|
{[1, 2, 3, 4].map((i) => (
|
|
468
511
|
<a key={i} href="#" className="group">
|
|
469
512
|
<div className="mb-4 h-40 rounded-xl gradient-bg opacity-70" />
|
|
470
|
-
<span className="text-xs uppercase tracking-wider text-
|
|
513
|
+
<span className="text-xs uppercase tracking-wider text-faint">Essay</span>
|
|
471
514
|
<h3 className="mt-1 text-xl font-semibold group-hover:opacity-90">A thoughtful headline for post {i}</h3>
|
|
472
|
-
<p className="mt-1 text-sm text-
|
|
515
|
+
<p className="mt-1 text-sm text-muted">Two lines of supporting copy to set the scene for the reader.</p>
|
|
473
516
|
</a>
|
|
474
517
|
))}
|
|
475
518
|
</div>
|
|
@@ -480,9 +523,9 @@ export default function Home() {
|
|
|
480
523
|
return shell(`${NAV(n, ["Guides", "API", "Examples"])}
|
|
481
524
|
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
482
525
|
<h1 className="text-5xl font-black leading-tight sm:text-6xl"><span className="gradient-text">${n}</span></h1>
|
|
483
|
-
<p className="mx-auto mt-6 max-w-2xl text-lg text-
|
|
484
|
-
<div className="mt-8 inline-flex items-center gap-3 rounded-lg border border-
|
|
485
|
-
<span className="text-
|
|
526
|
+
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
527
|
+
<div className="mt-8 inline-flex items-center gap-3 rounded-lg border border-hairline bg-panel px-4 py-3 font-mono text-sm text-muted">
|
|
528
|
+
<span className="text-faint">$</span> npm install @acme/sdk
|
|
486
529
|
</div>
|
|
487
530
|
</section>
|
|
488
531
|
<section id="guides" className="mx-auto max-w-6xl px-6 py-12">
|
|
@@ -492,9 +535,9 @@ export default function Home() {
|
|
|
492
535
|
{ t: "Guides", d: "Task-focused walkthroughs for the common paths." },
|
|
493
536
|
{ t: "API reference", d: "Every endpoint, typed, with copy-paste examples." },
|
|
494
537
|
].map((c) => (
|
|
495
|
-
<a key={c.t} href="#" className="rounded-2xl border border-
|
|
538
|
+
<a key={c.t} href="#" className="rounded-2xl border border-hairline bg-surface p-8 transition hover:border-hairline">
|
|
496
539
|
<h3 className="text-xl font-bold gradient-text">{c.t}</h3>
|
|
497
|
-
<p className="mt-2 text-
|
|
540
|
+
<p className="mt-2 text-muted">{c.d}</p>
|
|
498
541
|
</a>
|
|
499
542
|
))}
|
|
500
543
|
</div>
|
|
@@ -503,36 +546,39 @@ export default function Home() {
|
|
|
503
546
|
}
|
|
504
547
|
if (ctx.template.key === "dashboard") {
|
|
505
548
|
return shell(`<div className="flex min-h-screen">
|
|
506
|
-
<aside className="hidden w-56 shrink-0 border-r border-
|
|
507
|
-
<div className="mb-8
|
|
508
|
-
|
|
549
|
+
<aside className="hidden w-56 shrink-0 border-r border-hairline p-6 md:block">
|
|
550
|
+
<div className="mb-8 flex items-center justify-between">
|
|
551
|
+
<span className="text-lg font-black gradient-text">${n}</span>
|
|
552
|
+
<ThemeToggle />
|
|
553
|
+
</div>
|
|
554
|
+
<nav className="space-y-1 text-sm text-muted">
|
|
509
555
|
{["Overview", "Analytics", "Customers", "Settings"].map((l) => (
|
|
510
|
-
<a key={l} href="#" className="block rounded-lg px-3 py-2 hover:bg-
|
|
556
|
+
<a key={l} href="#" className="block rounded-lg px-3 py-2 hover:bg-surface hover:text-fg">{l}</a>
|
|
511
557
|
))}
|
|
512
558
|
</nav>
|
|
513
559
|
</aside>
|
|
514
560
|
<main className="flex-1 p-6 md:p-10">
|
|
515
561
|
<h1 className="text-2xl font-bold">Overview</h1>
|
|
516
|
-
<p className="mt-1 text-
|
|
562
|
+
<p className="mt-1 text-muted">${ctx.template.siteDescription}</p>
|
|
517
563
|
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
518
564
|
{[
|
|
519
565
|
{ l: "Revenue", v: "$48.2k", c: "+12%" }, { l: "Users", v: "12,480", c: "+3.4%" },
|
|
520
566
|
{ l: "Orders", v: "1,204", c: "+8%" }, { l: "Churn", v: "1.2%", c: "-0.3%" },
|
|
521
567
|
].map((s) => (
|
|
522
|
-
<div key={s.l} className="rounded-2xl border border-
|
|
523
|
-
<div className="text-sm text-
|
|
568
|
+
<div key={s.l} className="rounded-2xl border border-hairline bg-surface p-5">
|
|
569
|
+
<div className="text-sm text-muted">{s.l}</div>
|
|
524
570
|
<div className="mt-1 text-2xl font-bold">{s.v}</div>
|
|
525
571
|
<div className="mt-1 text-xs text-emerald-400">{s.c}</div>
|
|
526
572
|
</div>
|
|
527
573
|
))}
|
|
528
574
|
</div>
|
|
529
|
-
<div className="mt-6 rounded-2xl border border-
|
|
575
|
+
<div className="mt-6 rounded-2xl border border-hairline bg-surface p-6">
|
|
530
576
|
<div className="mb-4 font-semibold">Recent activity</div>
|
|
531
577
|
<div className="space-y-3">
|
|
532
578
|
{[1, 2, 3, 4].map((i) => (
|
|
533
|
-
<div key={i} className="flex items-center justify-between border-b border-
|
|
534
|
-
<span className="text-
|
|
535
|
-
<span className="text-
|
|
579
|
+
<div key={i} className="flex items-center justify-between border-b border-hairline pb-3 text-sm">
|
|
580
|
+
<span className="text-muted">Event #{i}</span>
|
|
581
|
+
<span className="text-faint">just now</span>
|
|
536
582
|
</div>
|
|
537
583
|
))}
|
|
538
584
|
</div>
|
|
@@ -543,9 +589,9 @@ export default function Home() {
|
|
|
543
589
|
if (ctx.template.key === "restaurant") {
|
|
544
590
|
return shell(`${NAV(n, ["Menu", "Story", "Reserve"])}
|
|
545
591
|
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
546
|
-
<p className="mb-4 text-sm font-semibold uppercase tracking-widest text-
|
|
592
|
+
<p className="mb-4 text-sm font-semibold uppercase tracking-widest text-faint">Est. 2026</p>
|
|
547
593
|
<h1 className="text-5xl font-black leading-tight sm:text-7xl gradient-text">${n}</h1>
|
|
548
|
-
<p className="mx-auto mt-6 max-w-xl text-lg text-
|
|
594
|
+
<p className="mx-auto mt-6 max-w-xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
549
595
|
<a href="#reserve" className="mt-10 inline-block gradient-bg rounded-full px-8 py-3 font-semibold text-black">Reserve a table</a>
|
|
550
596
|
</section>
|
|
551
597
|
<section id="menu" className="mx-auto max-w-4xl px-6 py-16">
|
|
@@ -555,10 +601,10 @@ export default function Home() {
|
|
|
555
601
|
{ n: "Charred leeks", p: "$14" }, { n: "Handmade tagliatelle", p: "$22" },
|
|
556
602
|
{ n: "Wood-fired trout", p: "$28" }, { n: "Olive oil cake", p: "$11" },
|
|
557
603
|
].map((d) => (
|
|
558
|
-
<div key={d.n} className="flex items-baseline justify-between gap-4 border-b border-
|
|
604
|
+
<div key={d.n} className="flex items-baseline justify-between gap-4 border-b border-hairline pb-4">
|
|
559
605
|
<div>
|
|
560
606
|
<h3 className="text-lg font-semibold">{d.n}</h3>
|
|
561
|
-
<p className="text-sm text-
|
|
607
|
+
<p className="text-sm text-muted">A short, mouth-watering description of the dish.</p>
|
|
562
608
|
</div>
|
|
563
609
|
<span className="shrink-0 gradient-text font-bold">{d.p}</span>
|
|
564
610
|
</div>
|
|
@@ -567,19 +613,19 @@ export default function Home() {
|
|
|
567
613
|
</section>
|
|
568
614
|
<section id="reserve" className="mx-auto max-w-2xl px-6 py-24 text-center">
|
|
569
615
|
<h2 className="text-3xl font-bold">Join us</h2>
|
|
570
|
-
<p className="mt-3 text-
|
|
571
|
-
<a href="tel:+10000000000" className="mt-8 inline-block rounded-full border border-
|
|
616
|
+
<p className="mt-3 text-muted">Open Wed\u2013Sun, 5pm till late. Walk-ins welcome; bookings recommended.</p>
|
|
617
|
+
<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>
|
|
572
618
|
</section>
|
|
573
619
|
${FOOTER(n)}`);
|
|
574
620
|
}
|
|
575
621
|
return shell(`${NAV(n, ["Features", "Pricing", "Sign in"])}
|
|
576
622
|
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
577
|
-
<p className="mb-4 inline-block rounded-full border border-
|
|
623
|
+
<p className="mb-4 inline-block rounded-full border border-hairline px-4 py-1 text-xs font-semibold text-muted">New \xB7 v1.0</p>
|
|
578
624
|
<h1 className="text-5xl font-black leading-tight sm:text-6xl">Ship faster with <span className="gradient-text">${n}</span></h1>
|
|
579
|
-
<p className="mx-auto mt-6 max-w-2xl text-lg text-
|
|
625
|
+
<p className="mx-auto mt-6 max-w-2xl text-lg text-muted">${ctx.template.siteDescription}</p>
|
|
580
626
|
<div className="mt-10 flex justify-center gap-4">
|
|
581
627
|
<a href="#pricing" className="gradient-bg rounded-full px-8 py-3 font-semibold text-black">Start free</a>
|
|
582
|
-
<a href="#features" className="rounded-full border border-
|
|
628
|
+
<a href="#features" className="rounded-full border border-hairline px-8 py-3 font-semibold hover:bg-surface">See features</a>
|
|
583
629
|
</div>
|
|
584
630
|
</section>
|
|
585
631
|
<section id="features" className="mx-auto max-w-6xl px-6 py-16">
|
|
@@ -589,17 +635,17 @@ export default function Home() {
|
|
|
589
635
|
{ t: "Secure", d: "Hardened headers and auth out of the box." },
|
|
590
636
|
{ t: "Scalable", d: "From your first user to your millionth." },
|
|
591
637
|
].map((f) => (
|
|
592
|
-
<div key={f.t} className="rounded-2xl border border-
|
|
638
|
+
<div key={f.t} className="rounded-2xl border border-hairline bg-surface p-8">
|
|
593
639
|
<h3 className="text-xl font-bold gradient-text">{f.t}</h3>
|
|
594
|
-
<p className="mt-2 text-
|
|
640
|
+
<p className="mt-2 text-muted">{f.d}</p>
|
|
595
641
|
</div>
|
|
596
642
|
))}
|
|
597
643
|
</div>
|
|
598
644
|
</section>
|
|
599
645
|
<section id="pricing" className="mx-auto max-w-md px-6 py-24 text-center">
|
|
600
|
-
<div className="rounded-3xl border border-
|
|
646
|
+
<div className="rounded-3xl border border-hairline bg-surface p-10">
|
|
601
647
|
<h2 className="text-3xl font-bold">Pro</h2>
|
|
602
|
-
<p className="mt-2 text-5xl font-black gradient-text">$29<span className="text-lg text-
|
|
648
|
+
<p className="mt-2 text-5xl font-black gradient-text">$29<span className="text-lg text-faint">/mo</span></p>
|
|
603
649
|
<a href="#" className="mt-8 inline-block w-full gradient-bg rounded-full px-8 py-3 font-semibold text-black">Get started</a>
|
|
604
650
|
</div>
|
|
605
651
|
</section>
|
|
@@ -655,9 +701,9 @@ export const metadata: Metadata = site.meta({
|
|
|
655
701
|
export default function AboutPage() {
|
|
656
702
|
return (
|
|
657
703
|
<main className="mx-auto max-w-3xl px-6 py-28">
|
|
658
|
-
<Link href="/" className="text-sm text-
|
|
704
|
+
<Link href="/" className="text-sm text-muted hover:text-fg">\u2190 Back home</Link>
|
|
659
705
|
<h1 className="mt-6 text-4xl font-black sm:text-5xl gradient-text">About</h1>
|
|
660
|
-
<p className="mt-6 text-lg leading-relaxed text-
|
|
706
|
+
<p className="mt-6 text-lg leading-relaxed text-muted">
|
|
661
707
|
This page already has its own SEO \u2014 a unique title, canonical URL, Open Graph tags and a
|
|
662
708
|
generated social image \u2014 from a single <code>site.meta()</code> call. Duplicate this file for
|
|
663
709
|
any new route and it just works.
|
|
@@ -666,6 +712,41 @@ export default function AboutPage() {
|
|
|
666
712
|
);
|
|
667
713
|
}
|
|
668
714
|
`;
|
|
715
|
+
var themeToggle = () => `"use client";
|
|
716
|
+
|
|
717
|
+
import { useTheme } from "@lacspace/theme";
|
|
718
|
+
import { useIsMounted } from "@lacspace/hooks";
|
|
719
|
+
|
|
720
|
+
/** A sun/moon button that flips between light and dark. */
|
|
721
|
+
export function ThemeToggle() {
|
|
722
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
723
|
+
const isMounted = useIsMounted();
|
|
724
|
+
const dark = resolvedTheme === "dark";
|
|
725
|
+
|
|
726
|
+
return (
|
|
727
|
+
<button
|
|
728
|
+
type="button"
|
|
729
|
+
aria-label="Toggle theme"
|
|
730
|
+
onClick={() => setTheme(dark ? "light" : "dark")}
|
|
731
|
+
className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-hairline text-muted transition hover:text-fg"
|
|
732
|
+
>
|
|
733
|
+
{/* Render a neutral placeholder until mounted to avoid a hydration mismatch. */}
|
|
734
|
+
{!isMounted() ? (
|
|
735
|
+
<span className="block h-4 w-4" />
|
|
736
|
+
) : dark ? (
|
|
737
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
738
|
+
<circle cx="12" cy="12" r="4" />
|
|
739
|
+
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41" />
|
|
740
|
+
</svg>
|
|
741
|
+
) : (
|
|
742
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
|
743
|
+
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
|
|
744
|
+
</svg>
|
|
745
|
+
)}
|
|
746
|
+
</button>
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
`;
|
|
669
750
|
var commandMenu = (ctx) => `"use client";
|
|
670
751
|
import { useRouter } from "next/navigation";
|
|
671
752
|
import { CommandPalette } from "@lacspace/ui";
|
|
@@ -721,8 +802,8 @@ import { useActionState } from "react";
|
|
|
721
802
|
import { honeypotProps, timestampValue } from "@lacspace/form";
|
|
722
803
|
import { submitContact, type ContactState } from "@/app/actions";
|
|
723
804
|
|
|
724
|
-
const field = "w-full rounded-xl border border-
|
|
725
|
-
const label = "mb-1 block text-sm text-
|
|
805
|
+
const field = "w-full rounded-xl border border-hairline bg-surface px-4 py-3 outline-none focus:border-hairline";
|
|
806
|
+
const label = "mb-1 block text-sm text-muted";
|
|
726
807
|
const errCls = "mt-1 text-sm text-red-400";
|
|
727
808
|
|
|
728
809
|
export function ContactForm() {
|
|
@@ -730,7 +811,7 @@ export function ContactForm() {
|
|
|
730
811
|
|
|
731
812
|
if (state?.ok) {
|
|
732
813
|
return (
|
|
733
|
-
<p className="rounded-2xl border border-
|
|
814
|
+
<p className="rounded-2xl border border-hairline bg-surface p-8 text-center text-lg">
|
|
734
815
|
Thanks \u2014 we’ll be in touch! \u2705
|
|
735
816
|
</p>
|
|
736
817
|
);
|
|
@@ -786,7 +867,7 @@ export default function ContactPage() {
|
|
|
786
867
|
return (
|
|
787
868
|
<main className="mx-auto max-w-2xl px-6 py-24">
|
|
788
869
|
<h1 className="text-4xl font-black gradient-text sm:text-5xl">Get in touch</h1>
|
|
789
|
-
<p className="mt-4 text-
|
|
870
|
+
<p className="mt-4 text-muted">Have a question or a project in mind? Drop a message below.</p>
|
|
790
871
|
<div className="mt-10">
|
|
791
872
|
<ContactForm />
|
|
792
873
|
</div>
|
|
@@ -885,7 +966,7 @@ export function DocsSidebar({ nav }: { nav: { group: string; items: DocMeta[] }[
|
|
|
885
966
|
<nav className="flex flex-col gap-6 text-sm">
|
|
886
967
|
{nav.map((group) => (
|
|
887
968
|
<div key={group.group}>
|
|
888
|
-
<p className="mb-2 text-xs font-semibold uppercase tracking-widest text-
|
|
969
|
+
<p className="mb-2 text-xs font-semibold uppercase tracking-widest text-faint">{group.group}</p>
|
|
889
970
|
<ul className="flex flex-col gap-1">
|
|
890
971
|
{group.items.map((d) => {
|
|
891
972
|
const href = \`/docs/\${d.slug}\`;
|
|
@@ -894,7 +975,7 @@ export function DocsSidebar({ nav }: { nav: { group: string; items: DocMeta[] }[
|
|
|
894
975
|
<li key={d.slug}>
|
|
895
976
|
<Link
|
|
896
977
|
href={href}
|
|
897
|
-
className={\`block rounded-lg px-3 py-1.5 transition \${active ? "gradient-bg font-semibold text-black" : "text-
|
|
978
|
+
className={\`block rounded-lg px-3 py-1.5 transition \${active ? "gradient-bg font-semibold text-black" : "text-muted hover:bg-surface hover:text-fg"}\`}
|
|
898
979
|
>
|
|
899
980
|
{d.title}
|
|
900
981
|
</Link>
|
|
@@ -911,13 +992,17 @@ export function DocsSidebar({ nav }: { nav: { group: string; items: DocMeta[] }[
|
|
|
911
992
|
var docsLayout = () => `import Link from "next/link";
|
|
912
993
|
import { getDocNav } from "@/lib/docs";
|
|
913
994
|
import { DocsSidebar } from "@/components/docs-sidebar";
|
|
995
|
+
import { ThemeToggle } from "@/components/theme-toggle";
|
|
914
996
|
|
|
915
997
|
export default function DocsLayout({ children }: { children: React.ReactNode }) {
|
|
916
998
|
const nav = getDocNav();
|
|
917
999
|
return (
|
|
918
1000
|
<div className="mx-auto grid max-w-6xl gap-10 px-6 py-16 md:grid-cols-[220px_1fr]">
|
|
919
1001
|
<aside className="md:sticky md:top-24 md:h-fit">
|
|
920
|
-
<
|
|
1002
|
+
<div className="mb-6 flex items-center justify-between">
|
|
1003
|
+
<Link href="/docs" className="block text-lg font-black gradient-text">Docs</Link>
|
|
1004
|
+
<ThemeToggle />
|
|
1005
|
+
</div>
|
|
921
1006
|
<DocsSidebar nav={nav} />
|
|
922
1007
|
</aside>
|
|
923
1008
|
<div className="min-w-0">{children}</div>
|
|
@@ -940,13 +1025,13 @@ export default function DocsIndex() {
|
|
|
940
1025
|
return (
|
|
941
1026
|
<div>
|
|
942
1027
|
<h1 className="text-4xl font-black gradient-text">Documentation</h1>
|
|
943
|
-
<p className="mt-4 text-
|
|
1028
|
+
<p className="mt-4 text-muted">Everything you need to get started and go deep.</p>
|
|
944
1029
|
<div className="mt-10 grid gap-4 sm:grid-cols-2">
|
|
945
1030
|
{docs.map((d) => (
|
|
946
|
-
<Link key={d.slug} href={\`/docs/\${d.slug}\`} className="rounded-2xl border border-
|
|
947
|
-
<span className="text-xs font-semibold uppercase tracking-widest text-
|
|
1031
|
+
<Link key={d.slug} href={\`/docs/\${d.slug}\`} className="rounded-2xl border border-hairline bg-surface p-5 transition hover:border-hairline">
|
|
1032
|
+
<span className="text-xs font-semibold uppercase tracking-widest text-faint">{d.group}</span>
|
|
948
1033
|
<h2 className="mt-1 text-lg font-bold">{d.title}</h2>
|
|
949
|
-
{d.description && <p className="mt-1 text-sm text-
|
|
1034
|
+
{d.description && <p className="mt-1 text-sm text-muted">{d.description}</p>}
|
|
950
1035
|
</Link>
|
|
951
1036
|
))}
|
|
952
1037
|
</div>
|
|
@@ -978,17 +1063,17 @@ export default async function DocPage({ params }: { params: Promise<{ slug: stri
|
|
|
978
1063
|
|
|
979
1064
|
return (
|
|
980
1065
|
<article>
|
|
981
|
-
<span className="text-xs font-semibold uppercase tracking-widest text-
|
|
1066
|
+
<span className="text-xs font-semibold uppercase tracking-widest text-faint">{doc.group}</span>
|
|
982
1067
|
<h1 className="mt-1 text-4xl font-black leading-tight">{doc.title}</h1>
|
|
983
|
-
{doc.description && <p className="mt-3 text-lg text-
|
|
1068
|
+
{doc.description && <p className="mt-3 text-lg text-muted">{doc.description}</p>}
|
|
984
1069
|
|
|
985
1070
|
{doc.toc.length > 2 && (
|
|
986
|
-
<div className="mt-8 rounded-xl border border-
|
|
987
|
-
<p className="mb-2 text-xs font-semibold uppercase tracking-widest text-
|
|
1071
|
+
<div className="mt-8 rounded-xl border border-hairline bg-surface p-4">
|
|
1072
|
+
<p className="mb-2 text-xs font-semibold uppercase tracking-widest text-faint">On this page</p>
|
|
988
1073
|
<ul className="flex flex-col gap-1 text-sm">
|
|
989
1074
|
{doc.toc.map((h) => (
|
|
990
1075
|
<li key={h.id} style={{ paddingLeft: (h.level - 1) * 12 }}>
|
|
991
|
-
<a href={\`#\${h.id}\`} className="text-
|
|
1076
|
+
<a href={\`#\${h.id}\`} className="text-muted hover:text-fg">{h.text}</a>
|
|
992
1077
|
</li>
|
|
993
1078
|
))}
|
|
994
1079
|
</ul>
|
|
@@ -997,9 +1082,9 @@ export default async function DocPage({ params }: { params: Promise<{ slug: stri
|
|
|
997
1082
|
|
|
998
1083
|
<div className="prose mt-10" dangerouslySetInnerHTML={{ __html: doc.html }} />
|
|
999
1084
|
|
|
1000
|
-
<nav className="mt-14 flex justify-between gap-4 border-t border-
|
|
1001
|
-
{prev ? <Link href={\`/docs/\${prev.slug}\`} className="text-
|
|
1002
|
-
{next ? <Link href={\`/docs/\${next.slug}\`} className="text-right text-
|
|
1085
|
+
<nav className="mt-14 flex justify-between gap-4 border-t border-hairline pt-8 text-sm">
|
|
1086
|
+
{prev ? <Link href={\`/docs/\${prev.slug}\`} className="text-muted hover:text-fg">← {prev.title}</Link> : <span />}
|
|
1087
|
+
{next ? <Link href={\`/docs/\${next.slug}\`} className="text-right text-muted hover:text-fg">{next.title} →</Link> : <span />}
|
|
1003
1088
|
</nav>
|
|
1004
1089
|
</article>
|
|
1005
1090
|
);
|
|
@@ -1208,17 +1293,17 @@ export default function Blog() {
|
|
|
1208
1293
|
return (
|
|
1209
1294
|
<main className="mx-auto max-w-3xl px-6 py-24">
|
|
1210
1295
|
<h1 className="text-4xl font-black gradient-text sm:text-5xl">Blog</h1>
|
|
1211
|
-
<p className="mt-4 text-
|
|
1296
|
+
<p className="mt-4 text-muted">Thoughts, notes and updates.</p>
|
|
1212
1297
|
<div className="mt-12 flex flex-col gap-8">
|
|
1213
1298
|
{posts.map((post) => (
|
|
1214
|
-
<Link key={post.slug} href={\`/blog/\${post.slug}\`} className="group rounded-2xl border border-
|
|
1215
|
-
{post.tag && <span className="text-xs font-semibold uppercase tracking-widest text-
|
|
1299
|
+
<Link key={post.slug} href={\`/blog/\${post.slug}\`} className="group rounded-2xl border border-hairline bg-surface p-6 transition hover:border-hairline">
|
|
1300
|
+
{post.tag && <span className="text-xs font-semibold uppercase tracking-widest text-faint">{post.tag}</span>}
|
|
1216
1301
|
<h2 className="mt-1 text-2xl font-bold group-hover:gradient-text">{post.title}</h2>
|
|
1217
|
-
<p className="mt-2 text-
|
|
1218
|
-
{post.date && <time className="mt-3 block text-sm text-
|
|
1302
|
+
<p className="mt-2 text-muted">{post.excerpt}</p>
|
|
1303
|
+
{post.date && <time className="mt-3 block text-sm text-faint">{new Date(post.date).toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })}</time>}
|
|
1219
1304
|
</Link>
|
|
1220
1305
|
))}
|
|
1221
|
-
{posts.length === 0 && <p className="text-
|
|
1306
|
+
{posts.length === 0 && <p className="text-muted">No posts yet \u2014 add a Markdown file in <code>content/posts/</code>.</p>}
|
|
1222
1307
|
</div>
|
|
1223
1308
|
</main>
|
|
1224
1309
|
);
|
|
@@ -1265,10 +1350,10 @@ export default async function PostPage({ params }: { params: Promise<{ slug: str
|
|
|
1265
1350
|
|
|
1266
1351
|
return (
|
|
1267
1352
|
<main className="mx-auto max-w-2xl px-6 py-24">
|
|
1268
|
-
<Link href="/blog" className="text-sm text-
|
|
1353
|
+
<Link href="/blog" className="text-sm text-muted hover:text-fg">← All posts</Link>
|
|
1269
1354
|
<article className="mt-6">
|
|
1270
1355
|
<h1 className="text-4xl font-black leading-tight sm:text-5xl">{post.title}</h1>
|
|
1271
|
-
{post.date && <time className="mt-4 block text-sm text-
|
|
1356
|
+
{post.date && <time className="mt-4 block text-sm text-faint">{new Date(post.date).toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })}</time>}
|
|
1272
1357
|
<div className="prose mt-10" dangerouslySetInnerHTML={{ __html: post.html }} />
|
|
1273
1358
|
</article>
|
|
1274
1359
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
|
@@ -1376,6 +1461,7 @@ function buildFiles(ctx) {
|
|
|
1376
1461
|
"app/actions.ts": actionsTs(),
|
|
1377
1462
|
"components/command-menu.tsx": commandMenu(ctx),
|
|
1378
1463
|
"components/contact-form.tsx": contactForm(),
|
|
1464
|
+
"components/theme-toggle.tsx": themeToggle(),
|
|
1379
1465
|
".github/workflows/seo.yml": seoWorkflow(),
|
|
1380
1466
|
".env.example": envExample(),
|
|
1381
1467
|
"WELCOME.md": welcomeMd(ctx)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Scaffold a beautiful, production-ready Next.js app from a Lacspace template — portfolio, business, e-commerce, SaaS, blog, docs, dashboard or restaurant — pre-wired with SEO, security headers, sitemap and robots. Like create-next-app, but you start gorgeous.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|