create-lacspace-app 1.0.0 → 1.2.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/README.md +14 -4
- package/dist/index.js +269 -7
- package/package.json +44 -10
package/README.md
CHANGED
|
@@ -29,14 +29,24 @@ It asks for a project name and a template, scaffolds the project, installs depen
|
|
|
29
29
|
| **business** | A professional company / agency site with services + CTA |
|
|
30
30
|
| **ecommerce** | A modern storefront home with a featured product grid |
|
|
31
31
|
| **saas** | A high-converting SaaS landing page with features + pricing |
|
|
32
|
+
| **blog** | An editorial blog / magazine home with a featured post + grid |
|
|
33
|
+
| **docs** | A documentation landing with quick-start + feature cards |
|
|
34
|
+
| **dashboard** | An admin app shell with a sidebar, stat cards and activity |
|
|
35
|
+
| **restaurant** | A warm restaurant / cafe site with a menu and reservations |
|
|
32
36
|
|
|
33
37
|
Every template is a real Next.js 15 App Router + Tailwind v4 project — dark, modern, responsive, with a gradient accent you can theme.
|
|
34
38
|
|
|
35
|
-
##
|
|
39
|
+
## A complete, polished project — not a blank page
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
41
|
+
Every generated app arrives finished, with the boring-but-essential things done:
|
|
42
|
+
|
|
43
|
+
- 🎨 **Beautiful home page** — Tailwind v4, the Inter font, dark theme + a gradient accent
|
|
44
|
+
- 🔎 **SEO** — one [`@lacspace/seo`](https://www.npmjs.com/package/@lacspace/seo) `defineSite()` config drives your `<title>`, canonical, Open Graph, Twitter card and JSON-LD
|
|
45
|
+
- ✨ **Dynamic OG images** — every page auto-generates a social-share card at `/og` (no design tool)
|
|
46
|
+
- 🛡️ **Security headers** — HSTS, CSP, X-Frame-Options and more via [`@lacspace/headers`](https://www.npmjs.com/package/@lacspace/headers)
|
|
47
|
+
- 🗺️ **robots.txt + sitemap.xml** — generated from your config with [`@lacspace/robots`](https://www.npmjs.com/package/@lacspace/robots) & [`@lacspace/sitemap`](https://www.npmjs.com/package/@lacspace/sitemap)
|
|
48
|
+
- 🧩 **A styled 404, a PWA manifest, and `.env.example`** — the finishing touches most starters skip
|
|
49
|
+
- 🎁 **A `WELCOME.md`** that walks you through everything and shows the bonus packages you can drop in
|
|
40
50
|
|
|
41
51
|
Edit `lib/site.ts` and `app/page.tsx` and you're off.
|
|
42
52
|
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,11 @@ var TEMPLATES = [
|
|
|
20
20
|
{ key: "personal", label: "Personal portfolio", description: "A sleek personal / developer portfolio with projects and contact.", accent: ["#6366f1", "#a855f7"], siteName: "Your Name", siteDescription: "Developer, designer & maker. Selected work and writing." },
|
|
21
21
|
{ key: "business", label: "Business site", description: "A professional company / agency site with services and a CTA.", accent: ["#2563eb", "#06b6d4"], siteName: "Acme Studio", siteDescription: "We design and build digital products that grow businesses." },
|
|
22
22
|
{ key: "ecommerce", label: "E-commerce storefront", description: "A modern product storefront home with a featured grid.", accent: ["#0d9488", "#84cc16"], siteName: "Acme Store", 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: "Acme Cloud", siteDescription: "The all-in-one platform your team will love. Ship faster." }
|
|
23
|
+
{ key: "saas", label: "SaaS landing", description: "A high-converting SaaS landing page with features and pricing.", accent: ["#7c3aed", "#ec4899"], siteName: "Acme Cloud", 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: "The Journal", 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: "Acme Docs", siteDescription: "Everything you need to build with Acme \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: "Acme Admin", 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: "Olive & Ember", siteDescription: "Seasonal plates, natural wine and a warm room. Book a table." }
|
|
24
28
|
];
|
|
25
29
|
var pkgJson = (ctx) => JSON.stringify({
|
|
26
30
|
name: ctx.name,
|
|
@@ -96,38 +100,59 @@ var globalsCss = (ctx) => `@import "tailwindcss";
|
|
|
96
100
|
:root {
|
|
97
101
|
--accent-from: ${ctx.template.accent[0]};
|
|
98
102
|
--accent-to: ${ctx.template.accent[1]};
|
|
103
|
+
--bg: #0a0a0f;
|
|
104
|
+
--fg: #e5e7eb;
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
* { border-color: rgb(255 255 255 / 0.08); }
|
|
101
108
|
html { scroll-behavior: smooth; }
|
|
102
|
-
body {
|
|
109
|
+
body {
|
|
110
|
+
background: var(--bg);
|
|
111
|
+
color: var(--fg);
|
|
112
|
+
-webkit-font-smoothing: antialiased;
|
|
113
|
+
text-rendering: optimizeLegibility;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
::selection { background: var(--accent-to); color: #0a0a0f; }
|
|
117
|
+
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
118
|
+
::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.12); border-radius: 8px; }
|
|
119
|
+
:focus-visible { outline: 2px solid var(--accent-to); outline-offset: 2px; border-radius: 4px; }
|
|
103
120
|
|
|
104
121
|
.gradient-text {
|
|
105
122
|
background: linear-gradient(120deg, var(--accent-from), var(--accent-to));
|
|
106
123
|
-webkit-background-clip: text; background-clip: text; color: transparent;
|
|
107
124
|
}
|
|
108
125
|
.gradient-bg { background: linear-gradient(120deg, var(--accent-from), var(--accent-to)); }
|
|
126
|
+
|
|
127
|
+
/* soft entrance for content */
|
|
128
|
+
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
|
|
129
|
+
main > section, main > * { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }
|
|
130
|
+
@media (prefers-reduced-motion: reduce) { *, ::before, ::after { animation: none !important; scroll-behavior: auto; } }
|
|
109
131
|
`;
|
|
110
132
|
var siteTs = (ctx) => `import { defineSite } from "@lacspace/seo";
|
|
111
133
|
|
|
112
134
|
/** Your site's SEO configuration \u2014 set once, used everywhere. */
|
|
113
135
|
export const site = defineSite({
|
|
114
136
|
name: ${JSON.stringify(ctx.template.siteName)},
|
|
115
|
-
url: "https://example.com",
|
|
137
|
+
url: process.env.NEXT_PUBLIC_SITE_URL ?? "https://example.com",
|
|
116
138
|
description: ${JSON.stringify(ctx.template.siteDescription)},
|
|
117
139
|
// twitter: "yourhandle",
|
|
118
|
-
|
|
140
|
+
ogImage: "/og", // \u2728 auto social-share images \u2014 see app/og/route.tsx
|
|
119
141
|
});
|
|
120
142
|
`;
|
|
121
143
|
var layout = (ctx) => `import type { Metadata } from "next";
|
|
144
|
+
import { Inter } from "next/font/google";
|
|
122
145
|
import { site } from "@/lib/site";
|
|
123
146
|
import "./globals.css";
|
|
124
147
|
|
|
148
|
+
const inter = Inter({ subsets: ["latin"], display: "swap" });
|
|
149
|
+
|
|
125
150
|
export const metadata: Metadata = site.meta({ title: ${JSON.stringify(ctx.template.siteName)} });
|
|
126
151
|
|
|
127
152
|
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
128
153
|
return (
|
|
129
|
-
<html lang="en">
|
|
130
|
-
<body>
|
|
154
|
+
<html lang="en" className={inter.className}>
|
|
155
|
+
<body className="antialiased">
|
|
131
156
|
{children}
|
|
132
157
|
<script
|
|
133
158
|
type="application/ld+json"
|
|
@@ -138,6 +163,113 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
|
|
138
163
|
);
|
|
139
164
|
}
|
|
140
165
|
`;
|
|
166
|
+
var ogRoute = (ctx) => `import { ImageResponse } from "next/og";
|
|
167
|
+
import { site } from "@/lib/site";
|
|
168
|
+
|
|
169
|
+
// \u2728 Dynamic Open Graph images \u2014 every page gets a gorgeous social card,
|
|
170
|
+
// generated on the fly at /og?title=Your+Page+Title. No design tool needed.
|
|
171
|
+
export const runtime = "edge";
|
|
172
|
+
|
|
173
|
+
export function GET(req: Request) {
|
|
174
|
+
const title = new URL(req.url).searchParams.get("title") ?? site.config.name;
|
|
175
|
+
return new ImageResponse(
|
|
176
|
+
(
|
|
177
|
+
<div
|
|
178
|
+
style={{
|
|
179
|
+
width: "100%", height: "100%", display: "flex", flexDirection: "column",
|
|
180
|
+
justifyContent: "center", padding: "90px",
|
|
181
|
+
background: "linear-gradient(135deg, ${ctx.template.accent[0]}, ${ctx.template.accent[1]})",
|
|
182
|
+
color: "white", fontFamily: "sans-serif",
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<div style={{ fontSize: 72, fontWeight: 800, lineHeight: 1.05, letterSpacing: "-0.02em" }}>{title}</div>
|
|
186
|
+
<div style={{ marginTop: 28, fontSize: 34, opacity: 0.85 }}>{site.config.name}</div>
|
|
187
|
+
</div>
|
|
188
|
+
),
|
|
189
|
+
{ width: 1200, height: 630 },
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
`;
|
|
193
|
+
var notFound = () => `import Link from "next/link";
|
|
194
|
+
|
|
195
|
+
export default function NotFound() {
|
|
196
|
+
return (
|
|
197
|
+
<main className="flex min-h-screen flex-col items-center justify-center gap-6 px-6 text-center">
|
|
198
|
+
<div className="text-8xl font-black gradient-text">404</div>
|
|
199
|
+
<p className="text-lg text-white/60">This page wandered off.</p>
|
|
200
|
+
<Link href="/" className="gradient-bg rounded-full px-6 py-3 font-semibold text-black">Back home</Link>
|
|
201
|
+
</main>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
`;
|
|
205
|
+
var manifestTs = (ctx) => `import type { MetadataRoute } from "next";
|
|
206
|
+
import { site } from "@/lib/site";
|
|
207
|
+
|
|
208
|
+
export default function manifest(): MetadataRoute.Manifest {
|
|
209
|
+
return {
|
|
210
|
+
name: site.config.name,
|
|
211
|
+
short_name: site.config.name,
|
|
212
|
+
description: site.config.description,
|
|
213
|
+
start_url: "/",
|
|
214
|
+
display: "standalone",
|
|
215
|
+
background_color: "#0a0a0f",
|
|
216
|
+
theme_color: "${ctx.template.accent[0]}",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
`;
|
|
220
|
+
var envExample = () => `# Your production URL \u2014 powers canonical URLs, sitemap, robots and OG images.
|
|
221
|
+
NEXT_PUBLIC_SITE_URL=https://example.com
|
|
222
|
+
`;
|
|
223
|
+
var welcomeMd = (ctx) => `# \u{1F381} Welcome to ${ctx.name}
|
|
224
|
+
|
|
225
|
+
You didn't get a blank page \u2014 you got a running, good-looking **${ctx.template.label}** with the
|
|
226
|
+
boring-but-essential stuff already done. Here's what's in the box.
|
|
227
|
+
|
|
228
|
+
## \u2705 Already set up for you
|
|
229
|
+
|
|
230
|
+
- **Beautiful home page** \u2014 styled with Tailwind v4, Inter font, dark theme + gradient accent.
|
|
231
|
+
- **SEO** \u2014 metadata, Open Graph, Twitter cards & JSON-LD, all from one file (\`lib/site.ts\`).
|
|
232
|
+
- **\u2728 Dynamic OG images** \u2014 every page auto-generates a social-share card at \`/og\`. Share a link and see.
|
|
233
|
+
- **Security headers** \u2014 HSTS, CSP, X-Frame-Options and more, via \`next.config.mjs\`.
|
|
234
|
+
- **robots.txt + sitemap.xml** \u2014 generated from your site config. No hand-editing.
|
|
235
|
+
- **A styled 404** and a **PWA manifest** \u2014 the finishing touches most starters skip.
|
|
236
|
+
|
|
237
|
+
## \u{1F680} Run it
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
npm run dev # http://localhost:3000
|
|
241
|
+
\`\`\`
|
|
242
|
+
|
|
243
|
+
## \u{1F3A8} Make it yours (start here)
|
|
244
|
+
|
|
245
|
+
1. Edit **\`lib/site.ts\`** \u2014 your name, URL and description flow into SEO, sitemap, robots and OG.
|
|
246
|
+
2. Edit **\`app/page.tsx\`** \u2014 your home page.
|
|
247
|
+
3. Set **\`NEXT_PUBLIC_SITE_URL\`** in \`.env\` before deploying (copy \`.env.example\`).
|
|
248
|
+
|
|
249
|
+
## \u{1F389} Surprise: 49 more Lacspace packages, one install away
|
|
250
|
+
|
|
251
|
+
Your app is wired for the whole ecosystem. Drop any of these in \u2014 all zero-dependency:
|
|
252
|
+
|
|
253
|
+
\`\`\`bash
|
|
254
|
+
npm i @lacspace/id # uuidv7, nanoid, short ids
|
|
255
|
+
npm i @lacspace/pdf # invoices & receipts, no headless browser
|
|
256
|
+
npm i @lacspace/signed-url # magic-login & expiring download links
|
|
257
|
+
npm i @lacspace/webhooks # sign / verify / deliver webhooks
|
|
258
|
+
npm i @lacspace/flags # feature flags & A/B, no SaaS
|
|
259
|
+
npm i @lacspace/humanize # "1.5 KB", "3 hours ago", "1.2M"
|
|
260
|
+
\`\`\`
|
|
261
|
+
|
|
262
|
+
Browse them all \u2192 **https://lacspace.com/packages**
|
|
263
|
+
|
|
264
|
+
## \u2601\uFE0F Deploy
|
|
265
|
+
|
|
266
|
+
Push to GitHub and import on **[Vercel](https://vercel.com/new)** \u2014 it just works. Remember to set
|
|
267
|
+
\`NEXT_PUBLIC_SITE_URL\` to your real domain.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
Built with \u2764\uFE0F using [Lacspace](https://lacspace.com/packages). This app is yours under the Lacspace Free Licence.
|
|
272
|
+
`;
|
|
141
273
|
var robotsTs = () => `import { robotsForSite } from "@lacspace/robots";
|
|
142
274
|
import { site } from "@/lib/site";
|
|
143
275
|
|
|
@@ -276,6 +408,131 @@ function homePage(ctx) {
|
|
|
276
408
|
</section>
|
|
277
409
|
${FOOTER(n)}`);
|
|
278
410
|
}
|
|
411
|
+
if (ctx.template.key === "blog") {
|
|
412
|
+
return shell(`${NAV(n, ["Latest", "Topics", "About"])}
|
|
413
|
+
<section className="mx-auto max-w-3xl px-6 py-24">
|
|
414
|
+
<p className="mb-3 text-sm font-semibold uppercase tracking-widest text-white/40">The Journal</p>
|
|
415
|
+
<h1 className="text-5xl font-black leading-tight sm:text-6xl gradient-text">${n}</h1>
|
|
416
|
+
<p className="mt-6 text-lg text-white/60">${ctx.template.siteDescription}</p>
|
|
417
|
+
</section>
|
|
418
|
+
<section id="latest" className="mx-auto max-w-5xl px-6 pb-8">
|
|
419
|
+
<a href="#" className="group block rounded-3xl border border-white/10 bg-white/[0.02] p-8 transition hover:border-white/20">
|
|
420
|
+
<div className="mb-6 h-56 rounded-2xl gradient-bg opacity-80" />
|
|
421
|
+
<span className="text-xs font-semibold uppercase tracking-wider text-white/40">Featured</span>
|
|
422
|
+
<h2 className="mt-2 text-3xl font-bold group-hover:opacity-90">The one thing every product needs before launch</h2>
|
|
423
|
+
<p className="mt-2 text-white/60">A short, punchy dek that pulls the reader into the piece and makes them want more.</p>
|
|
424
|
+
</a>
|
|
425
|
+
</section>
|
|
426
|
+
<section className="mx-auto max-w-5xl px-6 py-12">
|
|
427
|
+
<div className="grid gap-8 sm:grid-cols-2">
|
|
428
|
+
{[1, 2, 3, 4].map((i) => (
|
|
429
|
+
<a key={i} href="#" className="group">
|
|
430
|
+
<div className="mb-4 h-40 rounded-xl gradient-bg opacity-70" />
|
|
431
|
+
<span className="text-xs uppercase tracking-wider text-white/40">Essay</span>
|
|
432
|
+
<h3 className="mt-1 text-xl font-semibold group-hover:opacity-90">A thoughtful headline for post {i}</h3>
|
|
433
|
+
<p className="mt-1 text-sm text-white/50">Two lines of supporting copy to set the scene for the reader.</p>
|
|
434
|
+
</a>
|
|
435
|
+
))}
|
|
436
|
+
</div>
|
|
437
|
+
</section>
|
|
438
|
+
${FOOTER(n)}`);
|
|
439
|
+
}
|
|
440
|
+
if (ctx.template.key === "docs") {
|
|
441
|
+
return shell(`${NAV(n, ["Guides", "API", "Examples"])}
|
|
442
|
+
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
443
|
+
<h1 className="text-5xl font-black leading-tight sm:text-6xl"><span className="gradient-text">${n}</span></h1>
|
|
444
|
+
<p className="mx-auto mt-6 max-w-2xl text-lg text-white/60">${ctx.template.siteDescription}</p>
|
|
445
|
+
<div className="mt-8 inline-flex items-center gap-3 rounded-lg border border-white/10 bg-black/40 px-4 py-3 font-mono text-sm text-white/80">
|
|
446
|
+
<span className="text-white/40">$</span> npm install @acme/sdk
|
|
447
|
+
</div>
|
|
448
|
+
</section>
|
|
449
|
+
<section id="guides" className="mx-auto max-w-6xl px-6 py-12">
|
|
450
|
+
<div className="grid gap-6 md:grid-cols-3">
|
|
451
|
+
{[
|
|
452
|
+
{ t: "Quick start", d: "Go from zero to your first request in five minutes." },
|
|
453
|
+
{ t: "Guides", d: "Task-focused walkthroughs for the common paths." },
|
|
454
|
+
{ t: "API reference", d: "Every endpoint, typed, with copy-paste examples." },
|
|
455
|
+
].map((c) => (
|
|
456
|
+
<a key={c.t} href="#" className="rounded-2xl border border-white/10 bg-white/[0.02] p-8 transition hover:border-white/20">
|
|
457
|
+
<h3 className="text-xl font-bold gradient-text">{c.t}</h3>
|
|
458
|
+
<p className="mt-2 text-white/60">{c.d}</p>
|
|
459
|
+
</a>
|
|
460
|
+
))}
|
|
461
|
+
</div>
|
|
462
|
+
</section>
|
|
463
|
+
${FOOTER(n)}`);
|
|
464
|
+
}
|
|
465
|
+
if (ctx.template.key === "dashboard") {
|
|
466
|
+
return shell(`<div className="flex min-h-screen">
|
|
467
|
+
<aside className="hidden w-56 shrink-0 border-r border-white/10 p-6 md:block">
|
|
468
|
+
<div className="mb-8 text-lg font-black gradient-text">${n}</div>
|
|
469
|
+
<nav className="space-y-1 text-sm text-white/60">
|
|
470
|
+
{["Overview", "Analytics", "Customers", "Settings"].map((l) => (
|
|
471
|
+
<a key={l} href="#" className="block rounded-lg px-3 py-2 hover:bg-white/5 hover:text-white">{l}</a>
|
|
472
|
+
))}
|
|
473
|
+
</nav>
|
|
474
|
+
</aside>
|
|
475
|
+
<main className="flex-1 p-6 md:p-10">
|
|
476
|
+
<h1 className="text-2xl font-bold">Overview</h1>
|
|
477
|
+
<p className="mt-1 text-white/50">${ctx.template.siteDescription}</p>
|
|
478
|
+
<div className="mt-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
|
479
|
+
{[
|
|
480
|
+
{ l: "Revenue", v: "$48.2k", c: "+12%" }, { l: "Users", v: "12,480", c: "+3.4%" },
|
|
481
|
+
{ l: "Orders", v: "1,204", c: "+8%" }, { l: "Churn", v: "1.2%", c: "-0.3%" },
|
|
482
|
+
].map((s) => (
|
|
483
|
+
<div key={s.l} className="rounded-2xl border border-white/10 bg-white/[0.02] p-5">
|
|
484
|
+
<div className="text-sm text-white/50">{s.l}</div>
|
|
485
|
+
<div className="mt-1 text-2xl font-bold">{s.v}</div>
|
|
486
|
+
<div className="mt-1 text-xs text-emerald-400">{s.c}</div>
|
|
487
|
+
</div>
|
|
488
|
+
))}
|
|
489
|
+
</div>
|
|
490
|
+
<div className="mt-6 rounded-2xl border border-white/10 bg-white/[0.02] p-6">
|
|
491
|
+
<div className="mb-4 font-semibold">Recent activity</div>
|
|
492
|
+
<div className="space-y-3">
|
|
493
|
+
{[1, 2, 3, 4].map((i) => (
|
|
494
|
+
<div key={i} className="flex items-center justify-between border-b border-white/5 pb-3 text-sm">
|
|
495
|
+
<span className="text-white/70">Event #{i}</span>
|
|
496
|
+
<span className="text-white/40">just now</span>
|
|
497
|
+
</div>
|
|
498
|
+
))}
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
</main>
|
|
502
|
+
</div>`);
|
|
503
|
+
}
|
|
504
|
+
if (ctx.template.key === "restaurant") {
|
|
505
|
+
return shell(`${NAV(n, ["Menu", "Story", "Reserve"])}
|
|
506
|
+
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
507
|
+
<p className="mb-4 text-sm font-semibold uppercase tracking-widest text-white/40">Est. 2026</p>
|
|
508
|
+
<h1 className="text-5xl font-black leading-tight sm:text-7xl gradient-text">${n}</h1>
|
|
509
|
+
<p className="mx-auto mt-6 max-w-xl text-lg text-white/60">${ctx.template.siteDescription}</p>
|
|
510
|
+
<a href="#reserve" className="mt-10 inline-block gradient-bg rounded-full px-8 py-3 font-semibold text-black">Reserve a table</a>
|
|
511
|
+
</section>
|
|
512
|
+
<section id="menu" className="mx-auto max-w-4xl px-6 py-16">
|
|
513
|
+
<h2 className="mb-10 text-center text-3xl font-bold">Tonight's plates</h2>
|
|
514
|
+
<div className="grid gap-6 sm:grid-cols-2">
|
|
515
|
+
{[
|
|
516
|
+
{ n: "Charred leeks", p: "$14" }, { n: "Handmade tagliatelle", p: "$22" },
|
|
517
|
+
{ n: "Wood-fired trout", p: "$28" }, { n: "Olive oil cake", p: "$11" },
|
|
518
|
+
].map((d) => (
|
|
519
|
+
<div key={d.n} className="flex items-baseline justify-between gap-4 border-b border-white/10 pb-4">
|
|
520
|
+
<div>
|
|
521
|
+
<h3 className="text-lg font-semibold">{d.n}</h3>
|
|
522
|
+
<p className="text-sm text-white/50">A short, mouth-watering description of the dish.</p>
|
|
523
|
+
</div>
|
|
524
|
+
<span className="shrink-0 gradient-text font-bold">{d.p}</span>
|
|
525
|
+
</div>
|
|
526
|
+
))}
|
|
527
|
+
</div>
|
|
528
|
+
</section>
|
|
529
|
+
<section id="reserve" className="mx-auto max-w-2xl px-6 py-24 text-center">
|
|
530
|
+
<h2 className="text-3xl font-bold">Join us</h2>
|
|
531
|
+
<p className="mt-3 text-white/60">Open Wed\u2013Sun, 5pm till late. Walk-ins welcome; bookings recommended.</p>
|
|
532
|
+
<a href="tel:+10000000000" className="mt-8 inline-block rounded-full border border-white/20 px-8 py-3 font-semibold hover:bg-white/5">Call to book</a>
|
|
533
|
+
</section>
|
|
534
|
+
${FOOTER(n)}`);
|
|
535
|
+
}
|
|
279
536
|
return shell(`${NAV(n, ["Features", "Pricing", "Sign in"])}
|
|
280
537
|
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
281
538
|
<p className="mb-4 inline-block rounded-full border border-white/15 px-4 py-1 text-xs font-semibold text-white/60">New \xB7 v1.0</p>
|
|
@@ -321,8 +578,13 @@ function buildFiles(ctx) {
|
|
|
321
578
|
"app/layout.tsx": layout(ctx),
|
|
322
579
|
"app/globals.css": globalsCss(ctx),
|
|
323
580
|
"app/page.tsx": homePage(ctx),
|
|
581
|
+
"app/not-found.tsx": notFound(),
|
|
582
|
+
"app/og/route.tsx": ogRoute(ctx),
|
|
583
|
+
"app/manifest.ts": manifestTs(ctx),
|
|
324
584
|
"app/robots.txt/route.ts": robotsTs(),
|
|
325
|
-
"app/sitemap.xml/route.ts": sitemapTs()
|
|
585
|
+
"app/sitemap.xml/route.ts": sitemapTs(),
|
|
586
|
+
".env.example": envExample(),
|
|
587
|
+
"WELCOME.md": welcomeMd(ctx)
|
|
326
588
|
};
|
|
327
589
|
}
|
|
328
590
|
function parseArgs(list) {
|
package/package.json
CHANGED
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Scaffold a beautiful, production-ready Next.js app from a Lacspace template —
|
|
3
|
+
"version": "1.2.0",
|
|
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
|
-
"bin": {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-lacspace-app": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"create-lacspace-app",
|
|
18
|
+
"nextjs",
|
|
19
|
+
"next-app",
|
|
20
|
+
"starter",
|
|
21
|
+
"template",
|
|
22
|
+
"scaffold",
|
|
23
|
+
"boilerplate",
|
|
24
|
+
"portfolio-template",
|
|
25
|
+
"ecommerce-template",
|
|
26
|
+
"saas-template",
|
|
27
|
+
"blog-template",
|
|
28
|
+
"dashboard-template",
|
|
29
|
+
"docs-template",
|
|
30
|
+
"cli",
|
|
31
|
+
"create-next-app-alternative",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
10
34
|
"author": "Lacspace <contact@lacspace.com>",
|
|
11
35
|
"license": "SEE LICENSE IN LICENSE",
|
|
12
36
|
"homepage": "https://lacspace.com/packages",
|
|
13
|
-
"repository": {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "git+https://github.com/lacspace/npm-packages.git",
|
|
40
|
+
"directory": "create-lacspace-app"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/lacspace/npm-packages/issues"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
}
|
|
17
51
|
}
|