create-lacspace-app 1.14.1 → 1.15.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 +17 -0
- package/dist/index.js +265 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,23 @@ Edit `lib/site.ts` (your site config) and `app/page.tsx` — you're off.
|
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
|
+
## 🧩 Grow your app anytime — `add`
|
|
66
|
+
|
|
67
|
+
Unlike every other `create-*` tool, `create-lacspace-app` doesn't stop after day one. Drop **prebuilt, themed sections** into an existing app whenever you need them:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx create-lacspace-app add pricing faq testimonials
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
That writes ready-to-use sections into `components/sections/` (and installs the UI kit automatically if it's missing — so it works in **any** Next.js app, not just ones we scaffolded). Then:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { PricingSection } from "@/components/sections/pricing";
|
|
77
|
+
// …drop <PricingSection /> anywhere in your JSX.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Available sections:** `hero` · `features` · `pricing` · `faq` · `testimonials` · `team` · `stats` · `timeline` · `gallery` · `logos` · `cta` · `bento`. Run `add` with no arguments to list them.
|
|
81
|
+
|
|
65
82
|
## Templates
|
|
66
83
|
|
|
67
84
|
| Key | What you get |
|
package/dist/index.js
CHANGED
|
@@ -3462,6 +3462,7 @@ ${c("bold", "create-lacspace-app")} \u2014 scaffold a beautiful Next.js app, Lac
|
|
|
3462
3462
|
${c("bold", "Usage")}
|
|
3463
3463
|
npm create lacspace-app@latest <name> [options]
|
|
3464
3464
|
npx create-lacspace-app <name> --template <key>
|
|
3465
|
+
npx create-lacspace-app add <section...> ${c("dim", "# grow an existing app")}
|
|
3465
3466
|
|
|
3466
3467
|
${c("bold", "Templates")}
|
|
3467
3468
|
${TEMPLATES.map((t) => ` ${t.key.padEnd(10)} ${t.description}`).join("\n")}
|
|
@@ -3473,9 +3474,272 @@ ${c("bold", "Options")}
|
|
|
3473
3474
|
--no-git Skip git init
|
|
3474
3475
|
-y, --yes Accept defaults (needs <name>)
|
|
3475
3476
|
-h, --help Show this help
|
|
3477
|
+
|
|
3478
|
+
${c("bold", "Add sections to an existing app")}
|
|
3479
|
+
npx create-lacspace-app add pricing faq testimonials
|
|
3480
|
+
${c("dim", "Drops prebuilt, themed sections into components/sections/ (and the UI kit if missing).")}
|
|
3476
3481
|
`;
|
|
3482
|
+
var SECTIONS = {
|
|
3483
|
+
hero: `import Link from "next/link";
|
|
3484
|
+
import { Pill } from "@/components/ui";
|
|
3485
|
+
|
|
3486
|
+
export function HeroSection() {
|
|
3487
|
+
return (
|
|
3488
|
+
<section className="mx-auto max-w-4xl px-6 py-28 text-center">
|
|
3489
|
+
<Pill>New</Pill>
|
|
3490
|
+
<h1 className="mt-5 text-5xl font-bold sm:text-6xl">Build something <span className="gradient-text">people love</span></h1>
|
|
3491
|
+
<p className="mx-auto mt-5 max-w-xl text-lg text-muted">A confident headline and one sentence that sells the outcome \u2014 edit me.</p>
|
|
3492
|
+
<div className="mt-8 flex flex-wrap justify-center gap-3">
|
|
3493
|
+
<Link href="/contact" className="rounded-full gradient-bg px-7 py-3 font-semibold text-black transition hover:opacity-90">Get started</Link>
|
|
3494
|
+
<Link href="/about" className="rounded-full border border-hairline px-7 py-3 font-semibold transition hover:bg-surface">Learn more</Link>
|
|
3495
|
+
</div>
|
|
3496
|
+
</section>
|
|
3497
|
+
);
|
|
3498
|
+
}
|
|
3499
|
+
`,
|
|
3500
|
+
features: `import { Section, FeatureCard } from "@/components/ui";
|
|
3501
|
+
|
|
3502
|
+
const ITEMS = [
|
|
3503
|
+
{ icon: "\u26A1", title: "Fast", desc: "Server-rendered and instant by default." },
|
|
3504
|
+
{ icon: "\u{1F512}", title: "Secure", desc: "Sensible security headers out of the box." },
|
|
3505
|
+
{ icon: "\u{1F3A8}", title: "Beautiful", desc: "A polished, themeable design system." },
|
|
3506
|
+
{ icon: "\u{1F50E}", title: "SEO-ready", desc: "Metadata, JSON-LD and sitemaps wired." },
|
|
3507
|
+
{ icon: "\u{1F4F1}", title: "Responsive", desc: "Looks great on every screen." },
|
|
3508
|
+
{ icon: "\u{1F317}", title: "Dark mode", desc: "Light, dark and system \u2014 no flash." },
|
|
3509
|
+
];
|
|
3510
|
+
|
|
3511
|
+
export function FeaturesSection() {
|
|
3512
|
+
return (
|
|
3513
|
+
<Section eyebrow="Features" title="Everything you need">
|
|
3514
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
3515
|
+
{ITEMS.map((f) => <FeatureCard key={f.title} icon={f.icon} title={f.title} desc={f.desc} />)}
|
|
3516
|
+
</div>
|
|
3517
|
+
</Section>
|
|
3518
|
+
);
|
|
3519
|
+
}
|
|
3520
|
+
`,
|
|
3521
|
+
pricing: `import { Section, PricingTable } from "@/components/ui";
|
|
3522
|
+
|
|
3523
|
+
export function PricingSection() {
|
|
3524
|
+
return (
|
|
3525
|
+
<Section eyebrow="Pricing" title="Simple, transparent pricing">
|
|
3526
|
+
<PricingTable tiers={[
|
|
3527
|
+
{ name: "Starter", price: "$0", period: "mo", features: ["1 project", "Community support", "Basic analytics"] },
|
|
3528
|
+
{ name: "Pro", price: "$29", period: "mo", featured: true, features: ["Unlimited projects", "Priority support", "Advanced analytics", "Custom domain"] },
|
|
3529
|
+
{ name: "Scale", price: "Custom", features: ["SSO & SAML", "Dedicated support", "SLA & audit logs"] },
|
|
3530
|
+
]} />
|
|
3531
|
+
</Section>
|
|
3532
|
+
);
|
|
3533
|
+
}
|
|
3534
|
+
`,
|
|
3535
|
+
faq: `import { Section, FAQ } from "@/components/ui";
|
|
3536
|
+
|
|
3537
|
+
export function FaqSection() {
|
|
3538
|
+
return (
|
|
3539
|
+
<Section className="pt-0">
|
|
3540
|
+
<FAQ title="Frequently asked" items={[
|
|
3541
|
+
{ q: "Is it production-ready?", a: "Yes \u2014 server-rendered, SEO-optimized and secure by default." },
|
|
3542
|
+
{ q: "Can I customize it?", a: "Completely. Colours, fonts and layout are token-driven." },
|
|
3543
|
+
{ q: "Does it support dark mode?", a: "Light, dark and system themes with a no-flash script." },
|
|
3544
|
+
{ q: "How do I deploy?", a: "One click to any Node host or Vercel." },
|
|
3545
|
+
]} />
|
|
3546
|
+
</Section>
|
|
3547
|
+
);
|
|
3548
|
+
}
|
|
3549
|
+
`,
|
|
3550
|
+
testimonials: `import { Section, Testimonial } from "@/components/ui";
|
|
3551
|
+
|
|
3552
|
+
const QUOTES = [
|
|
3553
|
+
{ quote: "The smoothest launch we've ever had.", author: "Sam Rivera", role: "Product Lead" },
|
|
3554
|
+
{ quote: "Fast, polished and easy to build on.", author: "Jordan Ellis", role: "Founder" },
|
|
3555
|
+
{ quote: "The attention to detail shows everywhere.", author: "Priya Nair", role: "Design Director" },
|
|
3556
|
+
];
|
|
3557
|
+
|
|
3558
|
+
export function TestimonialsSection() {
|
|
3559
|
+
return (
|
|
3560
|
+
<Section eyebrow="Loved by teams" title="Don't just take our word for it">
|
|
3561
|
+
<div className="grid gap-6 md:grid-cols-3">
|
|
3562
|
+
{QUOTES.map((t) => <Testimonial key={t.author} quote={t.quote} author={t.author} role={t.role} />)}
|
|
3563
|
+
</div>
|
|
3564
|
+
</Section>
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
`,
|
|
3568
|
+
team: `import { Section, TeamGrid } from "@/components/ui";
|
|
3569
|
+
|
|
3570
|
+
export function TeamSection() {
|
|
3571
|
+
return (
|
|
3572
|
+
<Section eyebrow="The team" title="The people behind it">
|
|
3573
|
+
<TeamGrid members={[
|
|
3574
|
+
{ name: "Alex Morgan", role: "Founder & CEO", bio: "Sets the vision and keeps us honest." },
|
|
3575
|
+
{ name: "Riya Sharma", role: "Head of Design", bio: "Makes every pixel earn its place." },
|
|
3576
|
+
{ name: "Chris Doyle", role: "Lead Engineer", bio: "Ships fast without breaking things." },
|
|
3577
|
+
]} />
|
|
3578
|
+
</Section>
|
|
3579
|
+
);
|
|
3580
|
+
}
|
|
3581
|
+
`,
|
|
3582
|
+
stats: `import { Section, StatBand } from "@/components/ui";
|
|
3583
|
+
|
|
3584
|
+
export function StatsSection() {
|
|
3585
|
+
return (
|
|
3586
|
+
<Section eyebrow="By the numbers" title="Built to perform">
|
|
3587
|
+
<StatBand stats={[
|
|
3588
|
+
{ value: "10k+", label: "Monthly visitors" },
|
|
3589
|
+
{ value: "99.9%", label: "Uptime" },
|
|
3590
|
+
{ value: "4.9\u2605", label: "Average rating" },
|
|
3591
|
+
{ value: "<1s", label: "Load time" },
|
|
3592
|
+
]} />
|
|
3593
|
+
</Section>
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3596
|
+
`,
|
|
3597
|
+
timeline: `import { Section, Timeline } from "@/components/ui";
|
|
3598
|
+
|
|
3599
|
+
export function TimelineSection() {
|
|
3600
|
+
return (
|
|
3601
|
+
<Section eyebrow="Our story" title="How we got here">
|
|
3602
|
+
<div className="mx-auto max-w-2xl">
|
|
3603
|
+
<Timeline items={[
|
|
3604
|
+
{ date: "Then", title: "The idea", desc: "It started with a simple frustration." },
|
|
3605
|
+
{ date: "Next", title: "First release", desc: "We shipped, and people showed up." },
|
|
3606
|
+
{ date: "Now", title: "Growing fast", desc: "Thousands of teams, and counting." },
|
|
3607
|
+
]} />
|
|
3608
|
+
</div>
|
|
3609
|
+
</Section>
|
|
3610
|
+
);
|
|
3611
|
+
}
|
|
3612
|
+
`,
|
|
3613
|
+
gallery: `import { Section, Gallery } from "@/components/ui";
|
|
3614
|
+
|
|
3615
|
+
export function GallerySection() {
|
|
3616
|
+
return (
|
|
3617
|
+
<Section eyebrow="Gallery" title="A look inside">
|
|
3618
|
+
<Gallery items={[
|
|
3619
|
+
{ emoji: "\u{1F5BC}\uFE0F", label: "One" }, { emoji: "\u{1F306}", label: "Two" }, { emoji: "\u{1F3A8}", label: "Three" },
|
|
3620
|
+
{ emoji: "\u{1F4F8}", label: "Four" }, { emoji: "\u2728", label: "Five" }, { emoji: "\u{1F31F}", label: "Six" },
|
|
3621
|
+
]} />
|
|
3622
|
+
</Section>
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
`,
|
|
3626
|
+
logos: `import { Section, LogoCloud } from "@/components/ui";
|
|
3627
|
+
|
|
3628
|
+
export function LogosSection() {
|
|
3629
|
+
return (
|
|
3630
|
+
<Section className="pt-0">
|
|
3631
|
+
<LogoCloud label="Trusted by teams at" names={["Northwind", "Globex", "Initech", "Umbrella", "Soylent", "Hooli"]} />
|
|
3632
|
+
</Section>
|
|
3633
|
+
);
|
|
3634
|
+
}
|
|
3635
|
+
`,
|
|
3636
|
+
cta: `import { CTABand } from "@/components/ui";
|
|
3637
|
+
|
|
3638
|
+
export function CtaSection() {
|
|
3639
|
+
return <CTABand title="Ready to get started?" subtitle="Join thousands of teams building with us." ctaLabel="Get started" ctaHref="/contact" />;
|
|
3640
|
+
}
|
|
3641
|
+
`,
|
|
3642
|
+
bento: `import { Section, Bento } from "@/components/ui";
|
|
3643
|
+
|
|
3644
|
+
export function BentoSection() {
|
|
3645
|
+
return (
|
|
3646
|
+
<Section eyebrow="One platform" title="Everything, together">
|
|
3647
|
+
<Bento items={[
|
|
3648
|
+
{ icon: "\u26A1", title: "Blazing fast", desc: "Instant page loads, everywhere.", className: "sm:col-span-2" },
|
|
3649
|
+
{ icon: "\u{1F512}", title: "Secure", desc: "Locked down by default." },
|
|
3650
|
+
{ icon: "\u{1F3A8}", title: "Themeable", desc: "Your brand, one token away." },
|
|
3651
|
+
{ icon: "\u{1F4CA}", title: "Insightful", desc: "Know what's working.", className: "sm:col-span-2" },
|
|
3652
|
+
]} />
|
|
3653
|
+
</Section>
|
|
3654
|
+
);
|
|
3655
|
+
}
|
|
3656
|
+
`
|
|
3657
|
+
};
|
|
3658
|
+
function ensureUiKit(root) {
|
|
3659
|
+
const added = [];
|
|
3660
|
+
for (const [rel, content] of Object.entries(uiKitFiles())) {
|
|
3661
|
+
const full = join(root, rel);
|
|
3662
|
+
if (!existsSync(full)) {
|
|
3663
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
3664
|
+
writeFileSync(full, content);
|
|
3665
|
+
added.push(rel);
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
return added;
|
|
3669
|
+
}
|
|
3670
|
+
var pascal = (s) => s.split(/[-_]/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
3671
|
+
function runAdd(rawNames) {
|
|
3672
|
+
stdout.write(`
|
|
3673
|
+
${c("bold", c("magenta", "\u25C6 create-lacspace-app add"))} ${c("dim", "\u2014 drop prebuilt sections into your app")}
|
|
3674
|
+
|
|
3675
|
+
`);
|
|
3676
|
+
const root = cwd();
|
|
3677
|
+
if (!existsSync(join(root, "package.json"))) {
|
|
3678
|
+
stdout.write(c("red", "\u2717 No package.json here. Run this inside your Next.js project.\n\n"));
|
|
3679
|
+
exit(1);
|
|
3680
|
+
return;
|
|
3681
|
+
}
|
|
3682
|
+
const wanted = rawNames.filter((n) => !n.startsWith("-")).map((n) => n.toLowerCase());
|
|
3683
|
+
if (wanted.length === 0 || rawNames.includes("--help") || rawNames.includes("-h")) {
|
|
3684
|
+
stdout.write(`Usage: ${c("cyan", "npx create-lacspace-app add <section...>")}
|
|
3685
|
+
|
|
3686
|
+
${c("bold", "Available sections")}
|
|
3687
|
+
`);
|
|
3688
|
+
stdout.write(" " + Object.keys(SECTIONS).map((k) => c("cyan", k)).join(" ") + "\n");
|
|
3689
|
+
stdout.write(`
|
|
3690
|
+
Example: ${c("cyan", "npx create-lacspace-app add pricing faq testimonials")}
|
|
3691
|
+
|
|
3692
|
+
`);
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
const unknown = wanted.filter((n) => !(n in SECTIONS));
|
|
3696
|
+
if (unknown.length) stdout.write(c("yellow", `! Unknown: ${unknown.join(", ")} ${c("dim", "(run with no args to list)")}
|
|
3697
|
+
`));
|
|
3698
|
+
const toAdd = wanted.filter((n) => n in SECTIONS);
|
|
3699
|
+
if (toAdd.length === 0) {
|
|
3700
|
+
exit(1);
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
const kitAdded = ensureUiKit(root);
|
|
3704
|
+
if (kitAdded.length) stdout.write(` ${c("green", "\u2714")} Added the UI kit ${c("dim", "(" + kitAdded.length + " components)")}
|
|
3705
|
+
`);
|
|
3706
|
+
const created = [];
|
|
3707
|
+
for (const name of toAdd) {
|
|
3708
|
+
const rel = `components/sections/${name}.tsx`;
|
|
3709
|
+
const full = join(root, rel);
|
|
3710
|
+
if (existsSync(full)) {
|
|
3711
|
+
stdout.write(c("yellow", ` ~ ${rel} exists \u2014 skipped
|
|
3712
|
+
`));
|
|
3713
|
+
continue;
|
|
3714
|
+
}
|
|
3715
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
3716
|
+
writeFileSync(full, SECTIONS[name]);
|
|
3717
|
+
created.push(name);
|
|
3718
|
+
stdout.write(` ${c("green", "\u2714")} ${c("dim", "+ " + rel)}
|
|
3719
|
+
`);
|
|
3720
|
+
}
|
|
3721
|
+
if (created.length) {
|
|
3722
|
+
stdout.write(`
|
|
3723
|
+
${c("bold", "Use them")} \u2014 import into any page:
|
|
3724
|
+
`);
|
|
3725
|
+
for (const n of created) stdout.write(` ${c("cyan", `import { ${pascal(n)}Section } from "@/components/sections/${n}";`)}
|
|
3726
|
+
`);
|
|
3727
|
+
stdout.write(`
|
|
3728
|
+
Then drop ${c("cyan", "<" + pascal(created[0]) + "Section />")} into your JSX.
|
|
3729
|
+
`);
|
|
3730
|
+
stdout.write(`
|
|
3731
|
+
${c("dim", "Sections use the @lacspace UI kit + design tokens \u2014 best inside a create-lacspace-app project.")}
|
|
3732
|
+
|
|
3733
|
+
`);
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3477
3736
|
async function main() {
|
|
3478
|
-
const
|
|
3737
|
+
const raw = argv.slice(2);
|
|
3738
|
+
if (raw[0] === "add") {
|
|
3739
|
+
runAdd(raw.slice(1));
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
const args = parseArgs(raw);
|
|
3479
3743
|
if (args.help) {
|
|
3480
3744
|
stdout.write(HELP + "\n");
|
|
3481
3745
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.15.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": {
|