create-lacspace-app 1.12.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +500 -37
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -430,6 +430,7 @@ function homePage(ctx) {
430
430
  import { LiveStats } from "@/components/live-stats";
431
431
  import { Aurora } from "@/components/aurora";
432
432
  import { HeroArt } from "@/components/hero-art";
433
+ import { StatBand, Steps, Testimonial } from "@/components/ui";
433
434
 
434
435
  // \u2728 Self-canonical home page \u2014 one line, full SEO (title, canonical, OG, Twitter).
435
436
  export const metadata = site.meta({ title: ${JSON.stringify(n)}, path: "/" });
@@ -442,6 +443,7 @@ export default function Home() {
442
443
  ${inner}
443
444
  ${showcaseSection(ctx)}
444
445
  ${marqueeSection(ctx)}
446
+ ${extraHomeSections(ctx)}
445
447
  ${faqSection(ctx)}
446
448
  ${builtWithSection(ctx)}
447
449
  </main>
@@ -669,32 +671,138 @@ export default function AppleIcon() {
669
671
  );
670
672
  }
671
673
  `;
672
- var aboutPage = (ctx) => `import type { Metadata } from "next";
673
- import Link from "next/link";
674
- import { site } from "@/lib/site";
675
-
676
- // \u2728 Per-page SEO, auto-generated: title template, canonical, Open Graph,
677
- // Twitter card and its OWN dynamic OG image \u2014 all from one line.
678
- export const metadata: Metadata = site.meta({
679
- title: "About",
680
- path: "/about",
681
- description: "Learn more about ${ctx.template.siteName}.",
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&rsquo;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>`
682
805
  });
683
-
684
- export default function AboutPage() {
685
- return (
686
- <main className="mx-auto max-w-3xl px-6 py-28">
687
- <Link href="/" className="text-sm text-muted hover:text-fg">\u2190 Back home</Link>
688
- <h1 className="mt-6 text-4xl font-black sm:text-5xl gradient-text">About</h1>
689
- <p className="mt-6 text-lg leading-relaxed text-muted">
690
- This page already has its own SEO \u2014 a unique title, canonical URL, Open Graph tags and a
691
- generated social image \u2014 from a single <code>site.meta()</code> call. Duplicate this file for
692
- any new route and it just works.
693
- </p>
694
- </main>
695
- );
696
- }
697
- `;
698
806
  var themeToggle = () => `"use client";
699
807
 
700
808
  import { useTheme } from "@lacspace/theme";
@@ -1476,13 +1584,13 @@ function buildFiles(ctx) {
1476
1584
  "WELCOME.md": welcomeMd(ctx)
1477
1585
  };
1478
1586
  const isDash = ctx.template.key === "dashboard";
1587
+ files["components/aurora.tsx"] = aurora();
1479
1588
  if (isDash) {
1480
1589
  files["components/dashboard-shell.tsx"] = dashboardShell(ctx);
1481
1590
  } else {
1482
1591
  files["components/site-header.tsx"] = siteHeader(ctx);
1483
1592
  files["components/site-footer.tsx"] = siteFooter(ctx);
1484
1593
  files["components/announcement-bar.tsx"] = announcementBar();
1485
- files["components/aurora.tsx"] = aurora();
1486
1594
  files["components/hero-art.tsx"] = heroArt(ctx);
1487
1595
  }
1488
1596
  for (const page of stubPages(ctx)) {
@@ -1524,6 +1632,7 @@ function pagesFor(ctx) {
1524
1632
  { path: "/about", label: "About", nav: true, group: "Company", real: true },
1525
1633
  { path: "/contact", label: "Contact", nav: true, group: "Company", real: true },
1526
1634
  { path: "/careers", label: "Careers", group: "Company" },
1635
+ { path: "/faq", label: "FAQ", group: "Resources", real: true },
1527
1636
  { path: "/privacy", label: "Privacy", group: "Resources" },
1528
1637
  { path: "/terms", label: "Terms", group: "Resources" }
1529
1638
  ];
@@ -1531,13 +1640,12 @@ function pagesFor(ctx) {
1531
1640
  personal: [
1532
1641
  { path: "/work", label: "Work", nav: true, group: "Product", real: true },
1533
1642
  { path: "/blog", label: "Blog", nav: true, group: "Product" },
1534
- { path: "/uses", label: "Uses", group: "Resources" }
1643
+ { path: "/uses", label: "Uses", group: "Resources", real: true }
1535
1644
  ],
1536
1645
  business: [
1537
1646
  { path: "/services", label: "Services", nav: true, group: "Product", real: true },
1538
1647
  { path: "/work", label: "Work", nav: true, group: "Product" },
1539
- { path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true },
1540
- { path: "/faq", label: "FAQ", group: "Resources" }
1648
+ { path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true }
1541
1649
  ],
1542
1650
  ecommerce: [
1543
1651
  { path: "/shop", label: "Shop", nav: true, group: "Product", real: true },
@@ -1549,8 +1657,8 @@ function pagesFor(ctx) {
1549
1657
  saas: [
1550
1658
  { path: "/features", label: "Features", nav: true, group: "Product", real: true },
1551
1659
  { path: "/pricing", label: "Pricing", nav: true, group: "Product", real: true },
1552
- { path: "/integrations", label: "Integrations", group: "Product" },
1553
- { path: "/changelog", label: "Changelog", group: "Resources" }
1660
+ { path: "/integrations", label: "Integrations", group: "Product", real: true },
1661
+ { path: "/changelog", label: "Changelog", group: "Resources", real: true }
1554
1662
  ],
1555
1663
  blog: [
1556
1664
  { path: "/blog", label: "Articles", nav: true, group: "Product", real: true },
@@ -1561,12 +1669,12 @@ function pagesFor(ctx) {
1561
1669
  { path: "/docs", label: "Docs", nav: true, group: "Product", real: true },
1562
1670
  { path: "/guides", label: "Guides", nav: true, group: "Product", real: true },
1563
1671
  { path: "/api-reference", label: "API", nav: true, group: "Product" },
1564
- { path: "/changelog", label: "Changelog", group: "Resources" }
1672
+ { path: "/changelog", label: "Changelog", group: "Resources", real: true }
1565
1673
  ],
1566
1674
  restaurant: [
1567
1675
  { path: "/menu", label: "Menu", nav: true, group: "Product", real: true },
1568
1676
  { path: "/reservations", label: "Reservations", nav: true, group: "Product" },
1569
- { path: "/gallery", label: "Gallery", nav: true, group: "Product" },
1677
+ { path: "/gallery", label: "Gallery", nav: true, group: "Product", real: true },
1570
1678
  { path: "/events", label: "Private events", group: "Resources" }
1571
1679
  ]
1572
1680
  };
@@ -1946,7 +2054,7 @@ var pageFile = (o) => `import type { Metadata } from "next";
1946
2054
  import Link from "next/link";
1947
2055
  import { site } from "@/lib/site";
1948
2056
  import { Aurora } from "@/components/aurora";
1949
- 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";
1950
2058
 
1951
2059
  export const metadata: Metadata = site.meta({ title: ${JSON.stringify(o.title)}, path: ${JSON.stringify(o.path)}, description: ${JSON.stringify(o.description)} });
1952
2060
 
@@ -2560,6 +2668,244 @@ export function Newsletter({ title = "Stay in the loop", subtitle = "Occasional
2560
2668
  </div>
2561
2669
  );
2562
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
+ }
2563
2909
  `,
2564
2910
  "components/ui/index.ts": `export * from "./section";
2565
2911
  export * from "./pill";
@@ -2571,12 +2917,32 @@ export * from "./cta-band";
2571
2917
  export * from "./bento";
2572
2918
  export * from "./area-chart";
2573
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";
2574
2936
  `
2575
2937
  });
2576
2938
  function realPageFiles(ctx) {
2577
2939
  const k = ctx.template.key;
2578
2940
  const f = {};
2579
- if (k === "personal") f["app/work/page.tsx"] = workPage(ctx);
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
+ }
2580
2946
  if (k === "business") {
2581
2947
  f["app/services/page.tsx"] = servicesPage(ctx);
2582
2948
  f["app/pricing/page.tsx"] = pricingPage(ctx);
@@ -2584,6 +2950,8 @@ function realPageFiles(ctx) {
2584
2950
  if (k === "saas") {
2585
2951
  f["app/features/page.tsx"] = featuresPage(ctx);
2586
2952
  f["app/pricing/page.tsx"] = pricingPage(ctx);
2953
+ f["app/integrations/page.tsx"] = integrationsPage(ctx);
2954
+ f["app/changelog/page.tsx"] = changelogPage(ctx);
2587
2955
  }
2588
2956
  if (k === "ecommerce") {
2589
2957
  f["app/shop/page.tsx"] = shopPage(ctx);
@@ -2595,8 +2963,14 @@ function realPageFiles(ctx) {
2595
2963
  f["app/topics/page.tsx"] = topicsPage();
2596
2964
  f["app/newsletter/page.tsx"] = newsletterPage(ctx);
2597
2965
  }
2598
- if (k === "docs") f["app/guides/page.tsx"] = guidesPage();
2599
- if (k === "restaurant") f["app/menu/page.tsx"] = menuPage(ctx);
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
+ }
2600
2974
  if (k === "dashboard") {
2601
2975
  f["app/settings/page.tsx"] = settingsPage(ctx);
2602
2976
  f["components/settings-panel.tsx"] = settingsPanel();
@@ -2747,6 +3121,95 @@ var heroArt = (ctx) => {
2747
3121
  }
2748
3122
  `;
2749
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
+ };
2750
3213
  var showcaseSection = (ctx) => {
2751
3214
  const d = creativeData(ctx);
2752
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.12.2",
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": "./dist/index.js"
7
+ "create-lacspace-app": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"