create-lacspace-app 1.12.1 → 1.13.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 +515 -51
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -35,22 +35,23 @@ var pkgJson = (ctx) => JSON.stringify({
|
|
|
35
35
|
next: "^15.1.0",
|
|
36
36
|
react: "^19.0.0",
|
|
37
37
|
"react-dom": "^19.0.0",
|
|
38
|
-
"@lacspace/seo": "^1.6.
|
|
39
|
-
"@lacspace/headers": "^1.1.
|
|
40
|
-
"@lacspace/robots": "^1.2.
|
|
41
|
-
"@lacspace/sitemap": "^1.1.
|
|
42
|
-
"@lacspace/og": "^1.0.
|
|
43
|
-
"@lacspace/ui": "^1.0.
|
|
44
|
-
"@lacspace/form": "^1.0.
|
|
45
|
-
"@lacspace/validate": "^1.0.
|
|
38
|
+
"@lacspace/seo": "^1.6.2",
|
|
39
|
+
"@lacspace/headers": "^1.1.2",
|
|
40
|
+
"@lacspace/robots": "^1.2.1",
|
|
41
|
+
"@lacspace/sitemap": "^1.1.1",
|
|
42
|
+
"@lacspace/og": "^1.0.1",
|
|
43
|
+
"@lacspace/ui": "^1.0.1",
|
|
44
|
+
"@lacspace/form": "^1.0.1",
|
|
45
|
+
"@lacspace/validate": "^1.0.1",
|
|
46
46
|
// React Kit: dark/light theming (no-flash) + essential hooks + global
|
|
47
47
|
// state (announcement bar, mobile nav) + data fetching (live stats).
|
|
48
|
-
"@lacspace/theme": "^1.0.
|
|
49
|
-
"@lacspace/hooks": "^1.0.
|
|
50
|
-
"@lacspace/store": "^1.
|
|
51
|
-
"@lacspace/query": "^1.0.
|
|
52
|
-
// The blog & docs templates render Markdown with @lacspace/markdown
|
|
53
|
-
|
|
48
|
+
"@lacspace/theme": "^1.0.2",
|
|
49
|
+
"@lacspace/hooks": "^1.0.1",
|
|
50
|
+
"@lacspace/store": "^1.1.0",
|
|
51
|
+
"@lacspace/query": "^1.0.1",
|
|
52
|
+
// The blog & docs templates render Markdown with @lacspace/markdown
|
|
53
|
+
// (>=1.0.1 includes the URL-scheme XSS hardening — keep the floor there).
|
|
54
|
+
...ctx.template.key === "blog" || ctx.template.key === "docs" ? { "@lacspace/markdown": "^1.0.1" } : {}
|
|
54
55
|
},
|
|
55
56
|
devDependencies: {
|
|
56
57
|
typescript: "^5.7.0",
|
|
@@ -429,6 +430,7 @@ function homePage(ctx) {
|
|
|
429
430
|
import { LiveStats } from "@/components/live-stats";
|
|
430
431
|
import { Aurora } from "@/components/aurora";
|
|
431
432
|
import { HeroArt } from "@/components/hero-art";
|
|
433
|
+
import { StatBand, Steps, Testimonial } from "@/components/ui";
|
|
432
434
|
|
|
433
435
|
// \u2728 Self-canonical home page \u2014 one line, full SEO (title, canonical, OG, Twitter).
|
|
434
436
|
export const metadata = site.meta({ title: ${JSON.stringify(n)}, path: "/" });
|
|
@@ -441,6 +443,7 @@ export default function Home() {
|
|
|
441
443
|
${inner}
|
|
442
444
|
${showcaseSection(ctx)}
|
|
443
445
|
${marqueeSection(ctx)}
|
|
446
|
+
${extraHomeSections(ctx)}
|
|
444
447
|
${faqSection(ctx)}
|
|
445
448
|
${builtWithSection(ctx)}
|
|
446
449
|
</main>
|
|
@@ -668,32 +671,138 @@ export default function AppleIcon() {
|
|
|
668
671
|
);
|
|
669
672
|
}
|
|
670
673
|
`;
|
|
671
|
-
var aboutPage = (ctx) =>
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
674
|
+
var aboutPage = (ctx) => {
|
|
675
|
+
const r = richContent(ctx);
|
|
676
|
+
const n = ctx.template.siteName;
|
|
677
|
+
const story = r.process.map((p, i) => ({ date: ["Then", "Next", "Now", "Ahead"][i] ?? "", title: p.title, desc: p.desc }));
|
|
678
|
+
return pageFile({
|
|
679
|
+
title: "About",
|
|
680
|
+
path: "/about",
|
|
681
|
+
description: `The story, the team and the values behind ${n}.`,
|
|
682
|
+
cta: { title: "Let's build something together", subtitle: "We'd love to hear what you're working on.", label: "Get in touch", href: "/contact" },
|
|
683
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24">
|
|
684
|
+
<Breadcrumbs items={[{ label: "Home", href: "/" }, { label: "About" }]} />
|
|
685
|
+
<h1 className="mt-6 text-4xl font-bold sm:text-5xl">About <span className="gradient-text">${n}</span></h1>
|
|
686
|
+
<p className="mt-4 text-lg text-muted">${n} exists to help you ship something you're proud of \u2014 faster, and with less fuss. Here's who we are and what we believe.</p>
|
|
687
|
+
</section>
|
|
688
|
+
<Section className="pt-0"><StatBand stats={${JSON.stringify(r.stats)}} /></Section>
|
|
689
|
+
<Section eyebrow="Our story" title="How we got here" className="pt-0">
|
|
690
|
+
<div className="mx-auto max-w-2xl"><Timeline items={${JSON.stringify(story)}} /></div>
|
|
691
|
+
</Section>
|
|
692
|
+
<Section eyebrow="What we value" title="Principles we won't compromise" className="pt-0">
|
|
693
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
|
694
|
+
{${JSON.stringify(r.values)}.map((v) => <FeatureCard key={v.title} title={v.title} desc={v.desc} />)}
|
|
695
|
+
</div>
|
|
696
|
+
</Section>
|
|
697
|
+
<Section eyebrow="The team" title="The people behind ${n}" className="pt-0">
|
|
698
|
+
<TeamGrid members={${JSON.stringify(r.team)}} />
|
|
699
|
+
</Section>
|
|
700
|
+
<Section title="Frequently asked" className="pt-0"><FAQ items={${JSON.stringify(r.faqs.slice(0, 4))}} /></Section>`
|
|
701
|
+
});
|
|
702
|
+
};
|
|
703
|
+
var faqPage = (ctx) => {
|
|
704
|
+
const r = richContent(ctx);
|
|
705
|
+
const n = ctx.template.siteName;
|
|
706
|
+
return pageFile({
|
|
707
|
+
title: "FAQ",
|
|
708
|
+
path: "/faq",
|
|
709
|
+
description: `Answers to common questions about ${n}.`,
|
|
710
|
+
cta: { title: "Still have questions?", subtitle: "We're happy to help \u2014 reach out any time.", label: "Contact us", href: "/contact" },
|
|
711
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
712
|
+
<Pill>FAQ</Pill>
|
|
713
|
+
<h1 className="mt-4 text-4xl font-bold sm:text-5xl">Frequently asked <span className="gradient-text">questions</span></h1>
|
|
714
|
+
<p className="mx-auto mt-4 max-w-xl text-lg text-muted">Everything you need to know about ${n}. Can't find an answer? Get in touch.</p>
|
|
715
|
+
</section>
|
|
716
|
+
<Section className="pt-0"><FAQ items={${JSON.stringify(r.faqs)}} /></Section>`
|
|
717
|
+
});
|
|
718
|
+
};
|
|
719
|
+
var integrationsPage = (ctx) => pageFile({
|
|
720
|
+
title: "Integrations",
|
|
721
|
+
path: "/integrations",
|
|
722
|
+
description: `Connect ${ctx.template.siteName} to the tools your team already uses.`,
|
|
723
|
+
cta: { title: "Don't see your tool?", subtitle: "We add integrations every month \u2014 tell us what you need.", label: "Request an integration", href: "/contact" },
|
|
724
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
725
|
+
<Pill>Integrations</Pill>
|
|
726
|
+
<h1 className="mt-4 text-4xl font-bold sm:text-5xl">Works with your <span className="gradient-text">stack</span></h1>
|
|
727
|
+
<p className="mx-auto mt-4 max-w-xl text-lg text-muted">Plug ${ctx.template.siteName} into the tools you already love.</p>
|
|
728
|
+
</section>
|
|
729
|
+
<Section className="pt-0"><LogoCloud label="Popular integrations" names={["Slack", "Notion", "GitHub", "Figma", "Linear", "Stripe", "Zapier", "HubSpot"]} /></Section>
|
|
730
|
+
<Section eyebrow="Categories" title="Everything connects" className="pt-0">
|
|
731
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
732
|
+
{[
|
|
733
|
+
{ icon: "\u{1F4AC}", title: "Communication", desc: "Slack, Teams and email \u2014 never miss an update." },
|
|
734
|
+
{ icon: "\u{1F4CA}", title: "Analytics", desc: "Pipe events to your warehouse or BI tool." },
|
|
735
|
+
{ icon: "\u{1F517}", title: "Automation", desc: "Zapier and webhooks to connect anything." },
|
|
736
|
+
{ icon: "\u{1F4B3}", title: "Payments", desc: "Stripe and PayPal, wired and ready." },
|
|
737
|
+
{ icon: "\u{1F5C2}\uFE0F", title: "Productivity", desc: "Notion, Linear and Jira in two clicks." },
|
|
738
|
+
{ icon: "\u{1F510}", title: "Identity", desc: "SSO, SAML and SCIM for teams." },
|
|
739
|
+
].map((f) => <FeatureCard key={f.title} icon={f.icon} title={f.title} desc={f.desc} />)}
|
|
740
|
+
</div>
|
|
741
|
+
</Section>`
|
|
742
|
+
});
|
|
743
|
+
var changelogPage = (ctx) => pageFile({
|
|
744
|
+
title: "Changelog",
|
|
745
|
+
path: "/changelog",
|
|
746
|
+
description: `New features and improvements shipped to ${ctx.template.siteName}.`,
|
|
747
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
748
|
+
<Pill>Changelog</Pill>
|
|
749
|
+
<h1 className="mt-4 text-4xl font-bold sm:text-5xl">What’s <span className="gradient-text">new</span></h1>
|
|
750
|
+
<p className="mx-auto mt-4 max-w-xl text-lg text-muted">Every improvement we ship to ${ctx.template.siteName}, newest first.</p>
|
|
751
|
+
</section>
|
|
752
|
+
<Section className="pt-0">
|
|
753
|
+
<div className="mx-auto max-w-2xl">
|
|
754
|
+
<Timeline items={[
|
|
755
|
+
{ date: "This week", title: "Dark mode everywhere", desc: "A no-flash theme system across every page." },
|
|
756
|
+
{ date: "Last week", title: "Faster search", desc: "Command palette results now feel instant." },
|
|
757
|
+
{ date: "Earlier", title: "Team roles", desc: "Invite teammates with granular permissions." },
|
|
758
|
+
{ date: "Launch", title: "Hello, world \u{1F44B}", desc: "The first public release." },
|
|
759
|
+
]} />
|
|
760
|
+
</div>
|
|
761
|
+
</Section>`
|
|
762
|
+
});
|
|
763
|
+
var galleryPage = (ctx) => pageFile({
|
|
764
|
+
title: "Gallery",
|
|
765
|
+
path: "/gallery",
|
|
766
|
+
description: `A look inside ${ctx.template.siteName}.`,
|
|
767
|
+
cta: { title: "Come see for yourself", subtitle: "Book a table and taste the difference.", label: "Reserve a table", href: "/reservations" },
|
|
768
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
769
|
+
<Pill>Gallery</Pill>
|
|
770
|
+
<h1 className="mt-4 text-4xl font-bold sm:text-5xl">A look <span className="gradient-text">inside</span></h1>
|
|
771
|
+
<p className="mx-auto mt-4 max-w-xl text-lg text-muted">The room, the plates, the little details that make ${ctx.template.siteName}.</p>
|
|
772
|
+
</section>
|
|
773
|
+
<Section className="pt-0">
|
|
774
|
+
<Gallery items={[
|
|
775
|
+
{ emoji: "\u{1F37D}\uFE0F", label: "The dining room" },
|
|
776
|
+
{ emoji: "\u{1F958}", label: "Signature dish" },
|
|
777
|
+
{ emoji: "\u{1F377}", label: "Cellar" },
|
|
778
|
+
{ emoji: "\u{1F468}\u200D\u{1F373}", label: "The kitchen" },
|
|
779
|
+
{ emoji: "\u{1F370}", label: "Desserts" },
|
|
780
|
+
{ emoji: "\u{1F33F}", label: "Fresh daily" },
|
|
781
|
+
]} />
|
|
782
|
+
</Section>`
|
|
783
|
+
});
|
|
784
|
+
var usesPage = (ctx) => pageFile({
|
|
785
|
+
title: "Uses",
|
|
786
|
+
path: "/uses",
|
|
787
|
+
description: `The gear, apps and tools ${ctx.template.siteName} uses every day.`,
|
|
788
|
+
body: ` <section className="mx-auto max-w-3xl px-6 py-24 text-center">
|
|
789
|
+
<Pill>Uses</Pill>
|
|
790
|
+
<h1 className="mt-4 text-4xl font-bold sm:text-5xl">What I <span className="gradient-text">use</span></h1>
|
|
791
|
+
<p className="mx-auto mt-4 max-w-xl text-lg text-muted">The gear and software behind the work.</p>
|
|
792
|
+
</section>
|
|
793
|
+
<Section title="Editor & tools" className="pt-0">
|
|
794
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
795
|
+
{[
|
|
796
|
+
{ icon: "\u{1F5A5}\uFE0F", title: "VS Code", desc: "My editor of choice, tuned to the bone." },
|
|
797
|
+
{ icon: "\u26A1", title: "Next.js", desc: "The React framework behind everything I ship." },
|
|
798
|
+
{ icon: "\u{1F3A8}", title: "Figma", desc: "Where every design starts." },
|
|
799
|
+
{ icon: "\u2328\uFE0F", title: "Raycast", desc: "Launcher, clipboard history and snippets." },
|
|
800
|
+
{ icon: "\u{1F9E0}", title: "Obsidian", desc: "Notes and a second brain." },
|
|
801
|
+
{ icon: "\u{1F3A7}", title: "Focus playlist", desc: "Lo-fi, always." },
|
|
802
|
+
].map((f) => <FeatureCard key={f.title} icon={f.icon} title={f.title} desc={f.desc} />)}
|
|
803
|
+
</div>
|
|
804
|
+
</Section>`
|
|
681
805
|
});
|
|
682
|
-
|
|
683
|
-
export default function AboutPage() {
|
|
684
|
-
return (
|
|
685
|
-
<main className="mx-auto max-w-3xl px-6 py-28">
|
|
686
|
-
<Link href="/" className="text-sm text-muted hover:text-fg">\u2190 Back home</Link>
|
|
687
|
-
<h1 className="mt-6 text-4xl font-black sm:text-5xl gradient-text">About</h1>
|
|
688
|
-
<p className="mt-6 text-lg leading-relaxed text-muted">
|
|
689
|
-
This page already has its own SEO \u2014 a unique title, canonical URL, Open Graph tags and a
|
|
690
|
-
generated social image \u2014 from a single <code>site.meta()</code> call. Duplicate this file for
|
|
691
|
-
any new route and it just works.
|
|
692
|
-
</p>
|
|
693
|
-
</main>
|
|
694
|
-
);
|
|
695
|
-
}
|
|
696
|
-
`;
|
|
697
806
|
var themeToggle = () => `"use client";
|
|
698
807
|
|
|
699
808
|
import { useTheme } from "@lacspace/theme";
|
|
@@ -1475,13 +1584,13 @@ function buildFiles(ctx) {
|
|
|
1475
1584
|
"WELCOME.md": welcomeMd(ctx)
|
|
1476
1585
|
};
|
|
1477
1586
|
const isDash = ctx.template.key === "dashboard";
|
|
1587
|
+
files["components/aurora.tsx"] = aurora();
|
|
1478
1588
|
if (isDash) {
|
|
1479
1589
|
files["components/dashboard-shell.tsx"] = dashboardShell(ctx);
|
|
1480
1590
|
} else {
|
|
1481
1591
|
files["components/site-header.tsx"] = siteHeader(ctx);
|
|
1482
1592
|
files["components/site-footer.tsx"] = siteFooter(ctx);
|
|
1483
1593
|
files["components/announcement-bar.tsx"] = announcementBar();
|
|
1484
|
-
files["components/aurora.tsx"] = aurora();
|
|
1485
1594
|
files["components/hero-art.tsx"] = heroArt(ctx);
|
|
1486
1595
|
}
|
|
1487
1596
|
for (const page of stubPages(ctx)) {
|
|
@@ -1523,6 +1632,7 @@ function pagesFor(ctx) {
|
|
|
1523
1632
|
{ path: "/about", label: "About", nav: true, group: "Company", real: true },
|
|
1524
1633
|
{ path: "/contact", label: "Contact", nav: true, group: "Company", real: true },
|
|
1525
1634
|
{ path: "/careers", label: "Careers", group: "Company" },
|
|
1635
|
+
{ path: "/faq", label: "FAQ", group: "Resources", real: true },
|
|
1526
1636
|
{ path: "/privacy", label: "Privacy", group: "Resources" },
|
|
1527
1637
|
{ path: "/terms", label: "Terms", group: "Resources" }
|
|
1528
1638
|
];
|
|
@@ -1530,13 +1640,12 @@ function pagesFor(ctx) {
|
|
|
1530
1640
|
personal: [
|
|
1531
1641
|
{ path: "/work", label: "Work", nav: true, group: "Product", real: true },
|
|
1532
1642
|
{ path: "/blog", label: "Blog", nav: true, group: "Product" },
|
|
1533
|
-
{ path: "/uses", label: "Uses", group: "Resources" }
|
|
1643
|
+
{ path: "/uses", label: "Uses", group: "Resources", real: true }
|
|
1534
1644
|
],
|
|
1535
1645
|
business: [
|
|
1536
1646
|
{ path: "/services", label: "Services", nav: true, group: "Product", real: true },
|
|
1537
1647
|
{ path: "/work", label: "Work", nav: true, group: "Product" },
|
|
1538
|
-
{ path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true }
|
|
1539
|
-
{ path: "/faq", label: "FAQ", group: "Resources" }
|
|
1648
|
+
{ path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true }
|
|
1540
1649
|
],
|
|
1541
1650
|
ecommerce: [
|
|
1542
1651
|
{ path: "/shop", label: "Shop", nav: true, group: "Product", real: true },
|
|
@@ -1548,8 +1657,8 @@ function pagesFor(ctx) {
|
|
|
1548
1657
|
saas: [
|
|
1549
1658
|
{ path: "/features", label: "Features", nav: true, group: "Product", real: true },
|
|
1550
1659
|
{ path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true },
|
|
1551
|
-
{ path: "/integrations", label: "Integrations", group: "Product" },
|
|
1552
|
-
{ path: "/changelog", label: "Changelog", group: "Resources" }
|
|
1660
|
+
{ path: "/integrations", label: "Integrations", group: "Product", real: true },
|
|
1661
|
+
{ path: "/changelog", label: "Changelog", group: "Resources", real: true }
|
|
1553
1662
|
],
|
|
1554
1663
|
blog: [
|
|
1555
1664
|
{ path: "/blog", label: "Articles", nav: true, group: "Product", real: true },
|
|
@@ -1560,12 +1669,12 @@ function pagesFor(ctx) {
|
|
|
1560
1669
|
{ path: "/docs", label: "Docs", nav: true, group: "Product", real: true },
|
|
1561
1670
|
{ path: "/guides", label: "Guides", nav: true, group: "Product", real: true },
|
|
1562
1671
|
{ path: "/api-reference", label: "API", nav: true, group: "Product" },
|
|
1563
|
-
{ path: "/changelog", label: "Changelog", group: "Resources" }
|
|
1672
|
+
{ path: "/changelog", label: "Changelog", group: "Resources", real: true }
|
|
1564
1673
|
],
|
|
1565
1674
|
restaurant: [
|
|
1566
1675
|
{ path: "/menu", label: "Menu", nav: true, group: "Product", real: true },
|
|
1567
1676
|
{ path: "/reservations", label: "Reservations", nav: true, group: "Product" },
|
|
1568
|
-
{ path: "/gallery", label: "Gallery", nav: true, group: "Product" },
|
|
1677
|
+
{ path: "/gallery", label: "Gallery", nav: true, group: "Product", real: true },
|
|
1569
1678
|
{ path: "/events", label: "Private events", group: "Resources" }
|
|
1570
1679
|
]
|
|
1571
1680
|
};
|
|
@@ -1945,7 +2054,7 @@ var pageFile = (o) => `import type { Metadata } from "next";
|
|
|
1945
2054
|
import Link from "next/link";
|
|
1946
2055
|
import { site } from "@/lib/site";
|
|
1947
2056
|
import { Aurora } from "@/components/aurora";
|
|
1948
|
-
import { Section, Pill, StatCard, FeatureCard, Testimonial, Steps, CTABand, Bento, AreaChart, Newsletter } from "@/components/ui";
|
|
2057
|
+
import { Section, Pill, StatCard, FeatureCard, Testimonial, Steps, CTABand, Bento, AreaChart, Newsletter, Badge, Callout, Accordion, FAQ, Tabs, Timeline, PricingTable, LogoCloud, StatBand, TeamGrid, FeatureSplit, Gallery, Rating, Progress, Breadcrumbs, Avatar, AvatarGroup } from "@/components/ui";
|
|
1949
2058
|
|
|
1950
2059
|
export const metadata: Metadata = site.meta({ title: ${JSON.stringify(o.title)}, path: ${JSON.stringify(o.path)}, description: ${JSON.stringify(o.description)} });
|
|
1951
2060
|
|
|
@@ -2559,6 +2668,244 @@ export function Newsletter({ title = "Stay in the loop", subtitle = "Occasional
|
|
|
2559
2668
|
</div>
|
|
2560
2669
|
);
|
|
2561
2670
|
}
|
|
2671
|
+
`,
|
|
2672
|
+
"components/ui/badge.tsx": `import type { ReactNode } from "react";
|
|
2673
|
+
|
|
2674
|
+
const tones = {
|
|
2675
|
+
default: "border-hairline bg-surface text-muted",
|
|
2676
|
+
accent: "border-transparent gradient-bg text-black",
|
|
2677
|
+
success: "border-emerald-500/30 bg-emerald-500/10 text-emerald-400",
|
|
2678
|
+
warning: "border-amber-500/30 bg-amber-500/10 text-amber-400",
|
|
2679
|
+
danger: "border-red-500/30 bg-red-500/10 text-red-400",
|
|
2680
|
+
} as const;
|
|
2681
|
+
|
|
2682
|
+
export function Badge({ children, tone = "default", className = "" }: { children: ReactNode; tone?: keyof typeof tones; className?: string }) {
|
|
2683
|
+
return <span className={"inline-flex items-center gap-1 rounded-full border px-2.5 py-0.5 text-xs font-semibold " + tones[tone] + " " + className}>{children}</span>;
|
|
2684
|
+
}
|
|
2685
|
+
`,
|
|
2686
|
+
"components/ui/callout.tsx": `import type { ReactNode } from "react";
|
|
2687
|
+
|
|
2688
|
+
const tones = {
|
|
2689
|
+
info: { c: "border-sky-500/30 bg-sky-500/10", i: "\u{1F4A1}" },
|
|
2690
|
+
success: { c: "border-emerald-500/30 bg-emerald-500/10", i: "\u2705" },
|
|
2691
|
+
warning: { c: "border-amber-500/30 bg-amber-500/10", i: "\u26A0\uFE0F" },
|
|
2692
|
+
danger: { c: "border-red-500/30 bg-red-500/10", i: "\u26D4" },
|
|
2693
|
+
} as const;
|
|
2694
|
+
|
|
2695
|
+
export function Callout({ children, title, tone = "info" }: { children: ReactNode; title?: string; tone?: keyof typeof tones }) {
|
|
2696
|
+
const t = tones[tone];
|
|
2697
|
+
return (
|
|
2698
|
+
<div className={"flex gap-3 rounded-2xl border p-4 " + t.c}>
|
|
2699
|
+
<div className="text-lg leading-none">{t.i}</div>
|
|
2700
|
+
<div className="text-sm">
|
|
2701
|
+
{title ? <p className="font-semibold">{title}</p> : null}
|
|
2702
|
+
<div className="text-muted">{children}</div>
|
|
2703
|
+
</div>
|
|
2704
|
+
</div>
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
`,
|
|
2708
|
+
"components/ui/accordion.tsx": `export function Accordion({ items }: { items: { q: string; a: string }[] }) {
|
|
2709
|
+
return (
|
|
2710
|
+
<div className="overflow-hidden rounded-2xl border border-hairline bg-surface">
|
|
2711
|
+
{items.map((it, i) => (
|
|
2712
|
+
<details key={it.q} className={"group px-6 " + (i > 0 ? "border-t border-hairline" : "")}>
|
|
2713
|
+
<summary className="flex cursor-pointer list-none items-center justify-between py-4 font-medium">
|
|
2714
|
+
{it.q}
|
|
2715
|
+
<span className="ml-4 text-muted transition group-open:rotate-45">+</span>
|
|
2716
|
+
</summary>
|
|
2717
|
+
<p className="pb-5 text-sm leading-relaxed text-muted">{it.a}</p>
|
|
2718
|
+
</details>
|
|
2719
|
+
))}
|
|
2720
|
+
</div>
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
`,
|
|
2724
|
+
"components/ui/faq.tsx": `export function FAQ({ items, title }: { items: { q: string; a: string }[]; title?: string }) {
|
|
2725
|
+
return (
|
|
2726
|
+
<div className="mx-auto max-w-3xl">
|
|
2727
|
+
{title ? <h2 className="mb-8 text-center text-3xl font-bold sm:text-4xl">{title}</h2> : null}
|
|
2728
|
+
<div className="overflow-hidden rounded-2xl border border-hairline bg-surface">
|
|
2729
|
+
{items.map((it, i) => (
|
|
2730
|
+
<details key={it.q} className={"group px-6 " + (i > 0 ? "border-t border-hairline" : "")}>
|
|
2731
|
+
<summary className="flex cursor-pointer list-none items-center justify-between py-4 font-medium">{it.q}<span className="ml-4 text-muted transition group-open:rotate-45">+</span></summary>
|
|
2732
|
+
<p className="pb-5 text-sm leading-relaxed text-muted">{it.a}</p>
|
|
2733
|
+
</details>
|
|
2734
|
+
))}
|
|
2735
|
+
</div>
|
|
2736
|
+
</div>
|
|
2737
|
+
);
|
|
2738
|
+
}
|
|
2739
|
+
`,
|
|
2740
|
+
"components/ui/tabs.tsx": `"use client";
|
|
2741
|
+
|
|
2742
|
+
import { useState, type ReactNode } from "react";
|
|
2743
|
+
|
|
2744
|
+
export function Tabs({ tabs }: { tabs: { label: string; content: ReactNode }[] }) {
|
|
2745
|
+
const [i, setI] = useState(0);
|
|
2746
|
+
return (
|
|
2747
|
+
<div>
|
|
2748
|
+
<div className="inline-flex flex-wrap gap-1 rounded-full border border-hairline bg-surface p-1">
|
|
2749
|
+
{tabs.map((t, idx) => (
|
|
2750
|
+
<button key={t.label} onClick={() => setI(idx)} className={"rounded-full px-4 py-2 text-sm font-medium transition " + (i === idx ? "gradient-bg text-black" : "text-muted hover:text-fg")}>{t.label}</button>
|
|
2751
|
+
))}
|
|
2752
|
+
</div>
|
|
2753
|
+
<div className="mt-6">{tabs[i]?.content}</div>
|
|
2754
|
+
</div>
|
|
2755
|
+
);
|
|
2756
|
+
}
|
|
2757
|
+
`,
|
|
2758
|
+
"components/ui/timeline.tsx": `export function Timeline({ items }: { items: { date?: string; title: string; desc?: string }[] }) {
|
|
2759
|
+
return (
|
|
2760
|
+
<ol className="relative ml-3 border-l border-hairline">
|
|
2761
|
+
{items.map((it) => (
|
|
2762
|
+
<li key={it.title} className="mb-8 ml-6">
|
|
2763
|
+
<span aria-hidden className="absolute -left-[7px] mt-1.5 h-3 w-3 rounded-full gradient-bg" />
|
|
2764
|
+
{it.date ? <p className="text-xs font-semibold uppercase tracking-widest text-faint">{it.date}</p> : null}
|
|
2765
|
+
<h3 className="mt-1 font-semibold">{it.title}</h3>
|
|
2766
|
+
{it.desc ? <p className="mt-1 text-sm text-muted">{it.desc}</p> : null}
|
|
2767
|
+
</li>
|
|
2768
|
+
))}
|
|
2769
|
+
</ol>
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
`,
|
|
2773
|
+
"components/ui/pricing-table.tsx": `import Link from "next/link";
|
|
2774
|
+
|
|
2775
|
+
interface Tier { name: string; price: string; period?: string; features: string[]; ctaLabel?: string; ctaHref?: string; featured?: boolean; }
|
|
2776
|
+
|
|
2777
|
+
export function PricingTable({ tiers }: { tiers: Tier[] }) {
|
|
2778
|
+
return (
|
|
2779
|
+
<div className="grid gap-6 md:grid-cols-3">
|
|
2780
|
+
{tiers.map((t) => (
|
|
2781
|
+
<div key={t.name} className={"relative flex flex-col rounded-3xl border p-8 " + (t.featured ? "border-[color:var(--accent-to)] bg-surface shadow-lg" : "border-hairline bg-surface")}>
|
|
2782
|
+
{t.featured ? <span className="absolute -top-3 left-8 rounded-full gradient-bg px-3 py-1 text-xs font-bold text-black">Most popular</span> : null}
|
|
2783
|
+
<h3 className="font-semibold">{t.name}</h3>
|
|
2784
|
+
<div className="mt-3 flex items-end gap-1"><span className="text-4xl font-bold">{t.price}</span>{t.period ? <span className="mb-1 text-sm text-muted">/{t.period}</span> : null}</div>
|
|
2785
|
+
<ul className="mt-6 flex-1 space-y-2 text-sm text-muted">
|
|
2786
|
+
{t.features.map((f) => <li key={f} className="flex gap-2"><span className="gradient-text">\u2713</span>{f}</li>)}
|
|
2787
|
+
</ul>
|
|
2788
|
+
<Link href={t.ctaHref ?? "/contact"} className={"mt-8 rounded-full px-6 py-3 text-center font-semibold transition " + (t.featured ? "gradient-bg text-black hover:opacity-90" : "border border-hairline hover:bg-app")}>{t.ctaLabel ?? "Choose " + t.name}</Link>
|
|
2789
|
+
</div>
|
|
2790
|
+
))}
|
|
2791
|
+
</div>
|
|
2792
|
+
);
|
|
2793
|
+
}
|
|
2794
|
+
`,
|
|
2795
|
+
"components/ui/logo-cloud.tsx": `export function LogoCloud({ names, label }: { names: string[]; label?: string }) {
|
|
2796
|
+
return (
|
|
2797
|
+
<div className="text-center">
|
|
2798
|
+
{label ? <p className="text-sm font-semibold uppercase tracking-widest text-faint">{label}</p> : null}
|
|
2799
|
+
<div className="mt-6 flex flex-wrap items-center justify-center gap-x-10 gap-y-4">
|
|
2800
|
+
{names.map((n) => <span key={n} className="text-lg font-bold text-muted opacity-70 transition hover:opacity-100">{n}</span>)}
|
|
2801
|
+
</div>
|
|
2802
|
+
</div>
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
`,
|
|
2806
|
+
"components/ui/stat-band.tsx": `export function StatBand({ stats }: { stats: { value: string; label: string }[] }) {
|
|
2807
|
+
return (
|
|
2808
|
+
<div className="grid gap-6 rounded-3xl border border-hairline bg-surface p-8 sm:grid-cols-2 lg:grid-cols-4">
|
|
2809
|
+
{stats.map((s) => (
|
|
2810
|
+
<div key={s.label} className="text-center">
|
|
2811
|
+
<div className="gradient-text text-4xl font-bold tabular-nums">{s.value}</div>
|
|
2812
|
+
<div className="mt-1 text-sm text-muted">{s.label}</div>
|
|
2813
|
+
</div>
|
|
2814
|
+
))}
|
|
2815
|
+
</div>
|
|
2816
|
+
);
|
|
2817
|
+
}
|
|
2818
|
+
`,
|
|
2819
|
+
"components/ui/team-grid.tsx": `export function TeamGrid({ members }: { members: { name: string; role: string; bio?: string }[] }) {
|
|
2820
|
+
return (
|
|
2821
|
+
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
|
2822
|
+
{members.map((m) => (
|
|
2823
|
+
<div key={m.name} className="rounded-2xl border border-hairline bg-surface p-6 text-center transition hover:-translate-y-1">
|
|
2824
|
+
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full gradient-bg text-xl font-bold text-black">{m.name.split(" ").map((x) => x[0]).join("").slice(0, 2)}</div>
|
|
2825
|
+
<h3 className="mt-4 font-semibold">{m.name}</h3>
|
|
2826
|
+
<p className="gradient-text text-sm">{m.role}</p>
|
|
2827
|
+
{m.bio ? <p className="mt-2 text-sm text-muted">{m.bio}</p> : null}
|
|
2828
|
+
</div>
|
|
2829
|
+
))}
|
|
2830
|
+
</div>
|
|
2831
|
+
);
|
|
2832
|
+
}
|
|
2833
|
+
`,
|
|
2834
|
+
"components/ui/feature-split.tsx": `import type { ReactNode } from "react";
|
|
2835
|
+
|
|
2836
|
+
export function FeatureSplit({ eyebrow, title, desc, bullets, media, reverse = false }: { eyebrow?: string; title: string; desc?: string; bullets?: string[]; media?: ReactNode; reverse?: boolean }) {
|
|
2837
|
+
return (
|
|
2838
|
+
<div className={"grid items-center gap-10 md:grid-cols-2 " + (reverse ? "md:[&>*:first-child]:order-2" : "")}>
|
|
2839
|
+
<div>
|
|
2840
|
+
{eyebrow ? <p className="text-sm font-semibold uppercase tracking-widest text-faint">{eyebrow}</p> : null}
|
|
2841
|
+
<h2 className="mt-3 text-3xl font-bold sm:text-4xl">{title}</h2>
|
|
2842
|
+
{desc ? <p className="mt-4 text-lg text-muted">{desc}</p> : null}
|
|
2843
|
+
{bullets ? <ul className="mt-6 space-y-2 text-muted">{bullets.map((b) => <li key={b} className="flex gap-2"><span className="gradient-text">\u2713</span>{b}</li>)}</ul> : null}
|
|
2844
|
+
</div>
|
|
2845
|
+
<div className="relative overflow-hidden rounded-3xl border border-hairline bg-surface p-8">
|
|
2846
|
+
<div aria-hidden className="pointer-events-none absolute -right-10 -top-10 h-40 w-40 rounded-full gradient-bg opacity-30 blur-3xl" />
|
|
2847
|
+
<div className="relative">{media ?? <div className="grid h-48 place-items-center text-5xl">\u2728</div>}</div>
|
|
2848
|
+
</div>
|
|
2849
|
+
</div>
|
|
2850
|
+
);
|
|
2851
|
+
}
|
|
2852
|
+
`,
|
|
2853
|
+
"components/ui/gallery.tsx": `export function Gallery({ items }: { items: { label?: string; emoji?: string }[] }) {
|
|
2854
|
+
return (
|
|
2855
|
+
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
|
|
2856
|
+
{items.map((it, i) => (
|
|
2857
|
+
<div key={i} className="group relative grid aspect-square place-items-center overflow-hidden rounded-2xl border border-hairline bg-surface">
|
|
2858
|
+
<div aria-hidden className="pointer-events-none absolute inset-0 gradient-bg opacity-10 transition group-hover:opacity-20" />
|
|
2859
|
+
<span className="relative text-4xl">{it.emoji ?? "\u{1F5BC}\uFE0F"}</span>
|
|
2860
|
+
{it.label ? <span className="absolute bottom-2 left-3 text-xs text-muted">{it.label}</span> : null}
|
|
2861
|
+
</div>
|
|
2862
|
+
))}
|
|
2863
|
+
</div>
|
|
2864
|
+
);
|
|
2865
|
+
}
|
|
2866
|
+
`,
|
|
2867
|
+
"components/ui/rating.tsx": `export function Rating({ value = 5, count }: { value?: number; count?: number }) {
|
|
2868
|
+
return (
|
|
2869
|
+
<div className="inline-flex items-center gap-1 text-amber-400" aria-label={value + " out of 5"}>
|
|
2870
|
+
{[0, 1, 2, 3, 4].map((i) => <span key={i}>{i < Math.round(value) ? "\u2605" : "\u2606"}</span>)}
|
|
2871
|
+
{count ? <span className="ml-1 text-sm text-muted">({count})</span> : null}
|
|
2872
|
+
</div>
|
|
2873
|
+
);
|
|
2874
|
+
}
|
|
2875
|
+
`,
|
|
2876
|
+
"components/ui/progress.tsx": `export function Progress({ value, label }: { value: number; label?: string }) {
|
|
2877
|
+
const v = Math.max(0, Math.min(100, value));
|
|
2878
|
+
return (
|
|
2879
|
+
<div>
|
|
2880
|
+
{label ? <div className="mb-1 flex justify-between text-sm"><span>{label}</span><span className="text-muted">{v}%</span></div> : null}
|
|
2881
|
+
<div className="h-2 overflow-hidden rounded-full bg-app"><div className="h-full gradient-bg" style={{ width: v + "%" }} /></div>
|
|
2882
|
+
</div>
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
`,
|
|
2886
|
+
"components/ui/breadcrumbs.tsx": `import Link from "next/link";
|
|
2887
|
+
|
|
2888
|
+
export function Breadcrumbs({ items }: { items: { label: string; href?: string }[] }) {
|
|
2889
|
+
return (
|
|
2890
|
+
<nav aria-label="Breadcrumb" className="flex flex-wrap items-center gap-2 text-sm text-muted">
|
|
2891
|
+
{items.map((it, i) => (
|
|
2892
|
+
<span key={it.label} className="flex items-center gap-2">
|
|
2893
|
+
{it.href ? <Link href={it.href} className="hover:text-fg">{it.label}</Link> : <span className="text-fg">{it.label}</span>}
|
|
2894
|
+
{i < items.length - 1 ? <span aria-hidden className="text-faint">/</span> : null}
|
|
2895
|
+
</span>
|
|
2896
|
+
))}
|
|
2897
|
+
</nav>
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
`,
|
|
2901
|
+
"components/ui/avatar.tsx": `export function Avatar({ name, size = 40 }: { name: string; size?: number }) {
|
|
2902
|
+
const initials = name.split(" ").map((x) => x[0]).join("").slice(0, 2).toUpperCase();
|
|
2903
|
+
return <span className="inline-flex items-center justify-center rounded-full gradient-bg font-bold text-black" style={{ width: size, height: size, fontSize: size * 0.4 }}>{initials}</span>;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
export function AvatarGroup({ names }: { names: string[] }) {
|
|
2907
|
+
return <div className="flex -space-x-2">{names.map((n) => <span key={n} className="rounded-full bg-app p-0.5"><Avatar name={n} size={36} /></span>)}</div>;
|
|
2908
|
+
}
|
|
2562
2909
|
`,
|
|
2563
2910
|
"components/ui/index.ts": `export * from "./section";
|
|
2564
2911
|
export * from "./pill";
|
|
@@ -2570,12 +2917,32 @@ export * from "./cta-band";
|
|
|
2570
2917
|
export * from "./bento";
|
|
2571
2918
|
export * from "./area-chart";
|
|
2572
2919
|
export * from "./newsletter";
|
|
2920
|
+
export * from "./badge";
|
|
2921
|
+
export * from "./callout";
|
|
2922
|
+
export * from "./accordion";
|
|
2923
|
+
export * from "./faq";
|
|
2924
|
+
export * from "./tabs";
|
|
2925
|
+
export * from "./timeline";
|
|
2926
|
+
export * from "./pricing-table";
|
|
2927
|
+
export * from "./logo-cloud";
|
|
2928
|
+
export * from "./stat-band";
|
|
2929
|
+
export * from "./team-grid";
|
|
2930
|
+
export * from "./feature-split";
|
|
2931
|
+
export * from "./gallery";
|
|
2932
|
+
export * from "./rating";
|
|
2933
|
+
export * from "./progress";
|
|
2934
|
+
export * from "./breadcrumbs";
|
|
2935
|
+
export * from "./avatar";
|
|
2573
2936
|
`
|
|
2574
2937
|
});
|
|
2575
2938
|
function realPageFiles(ctx) {
|
|
2576
2939
|
const k = ctx.template.key;
|
|
2577
2940
|
const f = {};
|
|
2578
|
-
if (k
|
|
2941
|
+
if (k !== "dashboard") f["app/faq/page.tsx"] = faqPage(ctx);
|
|
2942
|
+
if (k === "personal") {
|
|
2943
|
+
f["app/work/page.tsx"] = workPage(ctx);
|
|
2944
|
+
f["app/uses/page.tsx"] = usesPage(ctx);
|
|
2945
|
+
}
|
|
2579
2946
|
if (k === "business") {
|
|
2580
2947
|
f["app/services/page.tsx"] = servicesPage(ctx);
|
|
2581
2948
|
f["app/pricing/page.tsx"] = pricingPage(ctx);
|
|
@@ -2583,6 +2950,8 @@ function realPageFiles(ctx) {
|
|
|
2583
2950
|
if (k === "saas") {
|
|
2584
2951
|
f["app/features/page.tsx"] = featuresPage(ctx);
|
|
2585
2952
|
f["app/pricing/page.tsx"] = pricingPage(ctx);
|
|
2953
|
+
f["app/integrations/page.tsx"] = integrationsPage(ctx);
|
|
2954
|
+
f["app/changelog/page.tsx"] = changelogPage(ctx);
|
|
2586
2955
|
}
|
|
2587
2956
|
if (k === "ecommerce") {
|
|
2588
2957
|
f["app/shop/page.tsx"] = shopPage(ctx);
|
|
@@ -2594,8 +2963,14 @@ function realPageFiles(ctx) {
|
|
|
2594
2963
|
f["app/topics/page.tsx"] = topicsPage();
|
|
2595
2964
|
f["app/newsletter/page.tsx"] = newsletterPage(ctx);
|
|
2596
2965
|
}
|
|
2597
|
-
if (k === "docs")
|
|
2598
|
-
|
|
2966
|
+
if (k === "docs") {
|
|
2967
|
+
f["app/guides/page.tsx"] = guidesPage();
|
|
2968
|
+
f["app/changelog/page.tsx"] = changelogPage(ctx);
|
|
2969
|
+
}
|
|
2970
|
+
if (k === "restaurant") {
|
|
2971
|
+
f["app/menu/page.tsx"] = menuPage(ctx);
|
|
2972
|
+
f["app/gallery/page.tsx"] = galleryPage(ctx);
|
|
2973
|
+
}
|
|
2599
2974
|
if (k === "dashboard") {
|
|
2600
2975
|
f["app/settings/page.tsx"] = settingsPage(ctx);
|
|
2601
2976
|
f["components/settings-panel.tsx"] = settingsPanel();
|
|
@@ -2746,6 +3121,95 @@ var heroArt = (ctx) => {
|
|
|
2746
3121
|
}
|
|
2747
3122
|
`;
|
|
2748
3123
|
};
|
|
3124
|
+
function richContent(ctx) {
|
|
3125
|
+
const n = ctx.template.siteName;
|
|
3126
|
+
const base = {
|
|
3127
|
+
stats: [
|
|
3128
|
+
{ value: "10k+", label: "Monthly visitors" },
|
|
3129
|
+
{ value: "99.9%", label: "Uptime" },
|
|
3130
|
+
{ value: "4.9\u2605", label: "Average rating" },
|
|
3131
|
+
{ value: "<1s", label: "Load time" }
|
|
3132
|
+
],
|
|
3133
|
+
process: [
|
|
3134
|
+
{ title: "Discover", desc: `We learn what ${n} needs and map the fastest path to value.` },
|
|
3135
|
+
{ title: "Design", desc: "We shape the experience and validate it early with real content." },
|
|
3136
|
+
{ title: "Build", desc: "We ship in weekly increments you can see and steer." },
|
|
3137
|
+
{ title: "Launch", desc: "We go live, measure, and keep improving together." }
|
|
3138
|
+
],
|
|
3139
|
+
testimonials: [
|
|
3140
|
+
{ quote: `Working with ${n} was the smoothest launch we've had \u2014 everything just worked.`, author: "Sam Rivera", role: "Product Lead" },
|
|
3141
|
+
{ quote: "Fast, polished and genuinely easy to build on. Highly recommend.", author: "Jordan Ellis", role: "Founder" },
|
|
3142
|
+
{ quote: "The attention to detail shows on every single page.", author: "Priya Nair", role: "Design Director" }
|
|
3143
|
+
],
|
|
3144
|
+
team: [
|
|
3145
|
+
{ name: "Alex Morgan", role: "Founder & CEO", bio: "Sets the vision and keeps us honest." },
|
|
3146
|
+
{ name: "Riya Sharma", role: "Head of Design", bio: "Makes every pixel earn its place." },
|
|
3147
|
+
{ name: "Chris Doyle", role: "Lead Engineer", bio: "Ships fast without breaking things." }
|
|
3148
|
+
],
|
|
3149
|
+
values: [
|
|
3150
|
+
{ title: "Craft", desc: "We care about the details others skip." },
|
|
3151
|
+
{ title: "Speed", desc: "We ship early and iterate in the open." },
|
|
3152
|
+
{ title: "Honesty", desc: "Clear pricing, clear timelines, no surprises." },
|
|
3153
|
+
{ title: "Partnership", desc: "Your goals are our goals." }
|
|
3154
|
+
],
|
|
3155
|
+
faqs: [
|
|
3156
|
+
{ q: `Is ${n} really production-ready?`, a: `Yes \u2014 every ${n} page is server-rendered, SEO-optimized and ships with sensible security headers out of the box.` },
|
|
3157
|
+
{ q: "How do I get started?", a: "Clone the project, run npm install and npm run dev \u2014 you'll have a live site in under a minute." },
|
|
3158
|
+
{ q: "Can I customize the design?", a: "Completely. Colours, fonts and layout are token-driven, so a few edits reskin the whole site." },
|
|
3159
|
+
{ q: "Is it accessible and fast?", a: "Built mobile-first with semantic HTML, keyboard support and a strong Lighthouse baseline." },
|
|
3160
|
+
{ q: "Do you support dark mode?", a: "Yes \u2014 light, dark and system themes with a no-flash script, powered by @lacspace/theme." },
|
|
3161
|
+
{ q: "How do I deploy?", a: "Deploy to any Node host or Vercel in one click \u2014 sitemap, robots and OG images are all wired up." }
|
|
3162
|
+
]
|
|
3163
|
+
};
|
|
3164
|
+
const over = {
|
|
3165
|
+
restaurant: {
|
|
3166
|
+
stats: [
|
|
3167
|
+
{ value: "20+", label: "Years serving" },
|
|
3168
|
+
{ value: "4.8\u2605", label: "Google rating" },
|
|
3169
|
+
{ value: "120", label: "Seats" },
|
|
3170
|
+
{ value: "Daily", label: "Fresh menu" }
|
|
3171
|
+
],
|
|
3172
|
+
process: [
|
|
3173
|
+
{ title: "Reserve", desc: `Book your table at ${n} online in seconds.` },
|
|
3174
|
+
{ title: "Arrive", desc: "Settle in \u2014 we'll take it from here." },
|
|
3175
|
+
{ title: "Savour", desc: "Seasonal plates, made from scratch." }
|
|
3176
|
+
],
|
|
3177
|
+
testimonials: [
|
|
3178
|
+
{ quote: `Best meal we've had all year \u2014 ${n} never misses.`, author: "Dana Lopez", role: "Regular" },
|
|
3179
|
+
{ quote: "The tasting menu is an experience. Book ahead!", author: "Marco Bianchi", role: "Food writer" },
|
|
3180
|
+
{ quote: "Warm service, unforgettable flavours.", author: "Aisha Khan", role: "Guest" }
|
|
3181
|
+
]
|
|
3182
|
+
},
|
|
3183
|
+
ecommerce: {
|
|
3184
|
+
stats: [
|
|
3185
|
+
{ value: "50k+", label: "Orders shipped" },
|
|
3186
|
+
{ value: "4.9\u2605", label: "Customer rating" },
|
|
3187
|
+
{ value: "48h", label: "Fast delivery" },
|
|
3188
|
+
{ value: "30d", label: "Free returns" }
|
|
3189
|
+
]
|
|
3190
|
+
}
|
|
3191
|
+
};
|
|
3192
|
+
return { ...base, ...over[ctx.template.key] ?? {} };
|
|
3193
|
+
}
|
|
3194
|
+
var extraHomeSections = (ctx) => {
|
|
3195
|
+
const r = richContent(ctx);
|
|
3196
|
+
const tst = r.testimonials.map((t) => `<Testimonial quote={${JSON.stringify(t.quote)}} author={${JSON.stringify(t.author)}} role={${JSON.stringify(t.role ?? "")}} />`).join("\n ");
|
|
3197
|
+
return `<section className="mx-auto max-w-6xl px-6 py-16">
|
|
3198
|
+
<p className="text-center text-sm font-semibold uppercase tracking-widest text-faint">By the numbers</p>
|
|
3199
|
+
<div className="mt-8"><StatBand stats={${JSON.stringify(r.stats)}} /></div>
|
|
3200
|
+
</section>
|
|
3201
|
+
<section className="mx-auto max-w-6xl px-6 py-16">
|
|
3202
|
+
<h2 className="text-center text-3xl font-bold sm:text-4xl">How it works</h2>
|
|
3203
|
+
<p className="mx-auto mt-3 max-w-xl text-center text-muted">A simple, proven path from first hello to launch day.</p>
|
|
3204
|
+
<div className="mt-10"><Steps items={${JSON.stringify(r.process)}} /></div>
|
|
3205
|
+
</section>
|
|
3206
|
+
<section className="mx-auto max-w-6xl px-6 py-16">
|
|
3207
|
+
<h2 className="text-center text-3xl font-bold sm:text-4xl">Loved by teams</h2>
|
|
3208
|
+
<div className="mt-10 grid gap-6 md:grid-cols-3">
|
|
3209
|
+
${tst}
|
|
3210
|
+
</div>
|
|
3211
|
+
</section>`;
|
|
3212
|
+
};
|
|
2749
3213
|
var showcaseSection = (ctx) => {
|
|
2750
3214
|
const d = creativeData(ctx);
|
|
2751
3215
|
const bullets = d.bullets.map((b) => `<li className="flex items-center gap-2 text-muted"><span className="text-[color:var(--accent-to)]">\u2713</span> ${b}</li>`).join("\n ");
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lacspace-app",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.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": {
|
|
7
|
-
"create-lacspace-app": "
|
|
7
|
+
"create-lacspace-app": "dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|