@goldlabelapps/theme 1.0.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 (63) hide show
  1. package/README.md +51 -0
  2. package/dist/components/download/DownloadBanner.d.ts +6 -0
  3. package/dist/components/download/DownloadBanner.js +37 -0
  4. package/dist/components/footer/Footer.d.ts +2 -0
  5. package/dist/components/footer/Footer.js +10 -0
  6. package/dist/components/guide/GuideLayout.d.ts +8 -0
  7. package/dist/components/guide/GuideLayout.js +12 -0
  8. package/dist/components/guide/MarkdownContent.d.ts +4 -0
  9. package/dist/components/guide/MarkdownContent.js +6 -0
  10. package/dist/components/hero/HeroSection.d.ts +7 -0
  11. package/dist/components/hero/HeroSection.js +12 -0
  12. package/dist/components/hero/ParticleCanvas.d.ts +7 -0
  13. package/dist/components/hero/ParticleCanvas.js +199 -0
  14. package/dist/components/hero/VideoModal.d.ts +11 -0
  15. package/dist/components/hero/VideoModal.js +42 -0
  16. package/dist/components/index.d.ts +23 -0
  17. package/dist/components/index.js +23 -0
  18. package/dist/components/navigation/DropdownMenu.d.ts +6 -0
  19. package/dist/components/navigation/DropdownMenu.js +67 -0
  20. package/dist/components/navigation/Header.d.ts +5 -0
  21. package/dist/components/navigation/Header.js +32 -0
  22. package/dist/components/navigation/LogoContextMenu.d.ts +7 -0
  23. package/dist/components/navigation/LogoContextMenu.js +61 -0
  24. package/dist/components/navigation/MobileMenu.d.ts +7 -0
  25. package/dist/components/navigation/MobileMenu.js +69 -0
  26. package/dist/components/navigation/ShareMenu.d.ts +7 -0
  27. package/dist/components/navigation/ShareMenu.js +75 -0
  28. package/dist/components/showcase/BlogSection.d.ts +8 -0
  29. package/dist/components/showcase/BlogSection.js +12 -0
  30. package/dist/components/showcase/FeatureExplorer.d.ts +9 -0
  31. package/dist/components/showcase/FeatureExplorer.js +36 -0
  32. package/dist/components/showcase/SolutionsSection.d.ts +6 -0
  33. package/dist/components/showcase/SolutionsSection.js +14 -0
  34. package/dist/components/showcase/StatementSection.d.ts +6 -0
  35. package/dist/components/showcase/StatementSection.js +9 -0
  36. package/dist/components/showcase/UseCaseSlider.d.ts +3 -0
  37. package/dist/components/showcase/UseCaseSlider.js +3 -0
  38. package/dist/components/showcase/VideoSection.d.ts +8 -0
  39. package/dist/components/showcase/VideoSection.js +28 -0
  40. package/dist/components/ui/Button.d.ts +10 -0
  41. package/dist/components/ui/Button.js +43 -0
  42. package/dist/components/ui/Card.d.ts +6 -0
  43. package/dist/components/ui/Card.js +15 -0
  44. package/dist/components/ui/CustomCursor.d.ts +8 -0
  45. package/dist/components/ui/CustomCursor.js +22 -0
  46. package/dist/components/ui/ThemeToggle.d.ts +5 -0
  47. package/dist/components/ui/ThemeToggle.js +4 -0
  48. package/dist/components/video/VideoLayout.d.ts +7 -0
  49. package/dist/components/video/VideoLayout.js +14 -0
  50. package/dist/context/ConfigContext.d.ts +9 -0
  51. package/dist/context/ConfigContext.js +114 -0
  52. package/dist/context/ThemeContext.d.ts +11 -0
  53. package/dist/context/ThemeContext.js +47 -0
  54. package/dist/context/index.d.ts +2 -0
  55. package/dist/context/index.js +2 -0
  56. package/dist/index.d.ts +4 -0
  57. package/dist/index.js +4 -0
  58. package/dist/lib/utils.d.ts +4 -0
  59. package/dist/lib/utils.js +21 -0
  60. package/dist/styles/globals.css +91 -0
  61. package/dist/types/index.d.ts +319 -0
  62. package/dist/types/index.js +1 -0
  63. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # @goldlabelapps/theme
2
+
3
+ > Shared Tailwind CSS v4 & Next.js theme tokens, UI primitives, and layout showcase blocks for Goldlabel applications.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @goldlabelapps/theme
9
+ # or
10
+ npm install @goldlabelapps/theme
11
+ ```
12
+
13
+ ## Setup & Styles
14
+
15
+ Import the global styles and CSS variables in your Next.js root layout:
16
+
17
+ ```tsx
18
+ import "@goldlabelapps/theme/styles.css";
19
+ import { ConfigProvider, ThemeProvider, Header, Footer } from "@goldlabelapps/theme";
20
+ import { siteConfig } from "@/config/site.config";
21
+
22
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
23
+ return (
24
+ <html lang="en">
25
+ <body>
26
+ <ConfigProvider value={siteConfig}>
27
+ <ThemeProvider>
28
+ <Header />
29
+ <main>{children}</main>
30
+ <Footer />
31
+ </ThemeProvider>
32
+ </ConfigProvider>
33
+ </body>
34
+ </html>
35
+ );
36
+ }
37
+ ```
38
+
39
+ ## Available Primitives & Blocks
40
+
41
+ - **Core UI**: `Button`, `Card`, `CustomCursorWrapper`, `ThemeToggle`
42
+ - **Hero & Canvas**: `HeroSection`, `ParticleCanvas`, `VideoModal`
43
+ - **Navigation & Footer**: `Header`, `Footer`, `DropdownMenu`, `MobileMenu`, `ShareMenu`, `BrandLogo`, `LogoContextMenu`
44
+ - **Showcase Blocks**: `FeatureExplorer`, `SolutionsSection`, `StatementSection`, `UseCaseSlider`, `VideoSection`, `BlogSection`
45
+ - **Lead Capture & Guides**: `DownloadBanner`, `GuideLayout`, `MarkdownContent`, `VideoLayout`
46
+ - **Context & Config**: `ConfigProvider`, `useSiteConfig`, `ThemeProvider`, `useTheme`
47
+ - **Utilities & Types**: `cn`, `detectUserOS`, full TypeScript definitions (`SiteConfig`, `BrandConfig`, etc.)
48
+
49
+ ## License
50
+
51
+ MIT © [Goldlabel Apps](https://github.com/goldlabelapps)
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import type { AuthCtaConfig } from "../../types";
3
+ export interface DownloadBannerProps {
4
+ config?: Partial<AuthCtaConfig>;
5
+ }
6
+ export declare function DownloadBanner({ config }: DownloadBannerProps): React.JSX.Element;
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import confetti from "canvas-confetti";
5
+ import { useSiteConfig } from "../../context/ConfigContext";
6
+ import { Button } from "../ui/Button";
7
+ import { Terminal, Copy, Check, Mail, CheckCircle2, } from "lucide-react";
8
+ export function DownloadBanner({ config }) {
9
+ const siteConfig = useSiteConfig();
10
+ const authCta = config ? { ...siteConfig.authCta, ...config } : siteConfig.authCta;
11
+ const [email, setEmail] = useState("");
12
+ const [submitted, setSubmitted] = useState(false);
13
+ const [copied, setCopied] = useState(false);
14
+ const handleEmailSubmit = (e) => {
15
+ e.preventDefault();
16
+ if (!email)
17
+ return;
18
+ confetti({
19
+ particleCount: 80,
20
+ spread: 70,
21
+ origin: { y: 0.85 },
22
+ colors: ["#012867", "#64748B", "#38BDF8", "#01358d", "#0F172A"],
23
+ });
24
+ setSubmitted(true);
25
+ };
26
+ const handleCopyCommand = async () => {
27
+ try {
28
+ await navigator.clipboard.writeText(authCta.cliQuickInstall.command);
29
+ setCopied(true);
30
+ setTimeout(() => setCopied(false), 2000);
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ };
36
+ return (_jsxs("section", { id: "download", className: "py-24 sm:py-36 relative bg-white overflow-hidden transition-colors duration-200", children: [_jsx("a", { id: "signup", "aria-hidden": "true", className: "sr-only", children: "Sign up" }), _jsx("div", { className: "pointer-events-none absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[700px] sm:w-[1000px] h-[500px] bg-gradient-to-tr from-[#012867]/15 via-slate-500/10 to-slate-400/10 rounded-full blur-3xl opacity-75" }), _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10", children: _jsxs("div", { className: "rounded-3xl sm:rounded-[40px] bg-gradient-to-b from-slate-50 via-white to-slate-50 border border-slate-200 p-8 sm:p-14 lg:p-20 shadow-2xl relative overflow-hidden text-center", children: [_jsx("div", { className: "inline-flex items-center gap-2 px-3.5 py-1 rounded-full bg-[#012867]/[0.06] border border-[#012867]/15 text-xs font-bold text-[#012867] mb-6 backdrop-blur-md", children: _jsx("span", { children: authCta.badge }) }), _jsx("h2", { className: "text-3xl sm:text-5xl lg:text-6xl font-extrabold text-slate-900 tracking-tight max-w-3xl mx-auto leading-tight mb-4", children: authCta.title }), _jsx("p", { className: "text-base sm:text-lg text-slate-600 max-w-2xl mx-auto leading-relaxed mb-10", children: authCta.subtitle }), !submitted ? (_jsxs("form", { onSubmit: handleEmailSubmit, className: "max-w-md mx-auto mb-8 flex flex-col sm:flex-row gap-3", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Mail, { className: "absolute left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" }), _jsx("input", { type: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "Enter your business email", className: "w-full pl-11 pr-4 py-3 rounded-full bg-white border border-slate-300 text-slate-900 text-sm focus:outline-none focus:ring-2 focus:ring-[#012867] shadow-inner" })] }), _jsx(Button, { type: "submit", variant: "primary", size: "md", children: "Get Started" })] })) : (_jsxs("div", { className: "max-w-md mx-auto mb-8 p-4 rounded-2xl bg-emerald-50 border border-emerald-200 text-emerald-800 text-sm font-semibold flex items-center justify-center gap-2", children: [_jsx(CheckCircle2, { className: "h-5 w-5 text-emerald-600" }), _jsxs("span", { children: ["Setup guide & download dispatched to ", email] })] })), _jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 mb-8", children: [_jsx(Button, { href: authCta.primaryCta.href, variant: "primary", size: "lg", children: authCta.primaryCta.label }), _jsx(Button, { href: authCta.secondaryCta.href, variant: "secondary", size: "lg", children: authCta.secondaryCta.label })] }), authCta.cliQuickInstall && (_jsxs("div", { className: "inline-flex items-center gap-3 px-4 py-2 rounded-2xl bg-slate-900 text-slate-200 text-xs font-mono border border-slate-800 shadow-md", children: [_jsx(Terminal, { className: "h-4 w-4 text-emerald-400" }), _jsx("span", { children: authCta.cliQuickInstall.command }), _jsx("button", { type: "button", onClick: handleCopyCommand, className: "p-1 text-slate-400 hover:text-white transition-colors cursor-pointer", children: copied ? _jsx(Check, { className: "h-3.5 w-3.5 text-emerald-400" }) : _jsx(Copy, { className: "h-3.5 w-3.5" }) })] }))] }) })] }));
37
+ }
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function Footer(): React.JSX.Element;
@@ -0,0 +1,10 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { useSiteConfig } from "../../context/ConfigContext";
5
+ import { BrandLogo } from "../navigation/LogoContextMenu";
6
+ import { ExternalLink } from "lucide-react";
7
+ export function Footer() {
8
+ const siteConfig = useSiteConfig();
9
+ return (_jsx("footer", { className: "bg-slate-50 border-t border-slate-200 text-slate-500 text-xs sm:text-sm transition-colors duration-200", children: _jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-16 sm:py-20", children: [_jsxs("div", { className: "flex flex-col md:flex-row items-start md:items-center justify-between gap-6 pb-12 border-b border-slate-200", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(BrandLogo, {}), _jsx("span", { className: "text-slate-300 font-mono text-xs", children: "\u2022" }), _jsx("span", { className: "text-slate-700 font-medium", children: siteConfig.footer.tagline })] }), _jsx("div", { className: "text-xs text-slate-500", children: siteConfig.footer.address || "Goldlabel Apps" })] }), _jsx("div", { className: "grid grid-cols-2 md:grid-cols-4 gap-8 py-12", children: siteConfig.footer.columns.map((col, idx) => (_jsxs("div", { className: "space-y-4", children: [_jsx("h4", { className: "text-xs font-bold text-slate-900 uppercase tracking-wider", children: col.title }), _jsx("ul", { className: "space-y-2.5", children: col.links.map((link, lIdx) => (_jsx("li", { children: link.external ? (_jsxs("a", { href: link.href, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-slate-600 hover:text-[#012867] transition-colors", children: [_jsx("span", { children: link.label }), _jsx(ExternalLink, { className: "h-3 w-3 text-slate-400" })] })) : (_jsx(Link, { href: link.href, className: "text-slate-600 hover:text-[#012867] transition-colors", children: link.label })) }, lIdx))) })] }, idx))) }), _jsxs("div", { className: "pt-8 border-t border-slate-200 flex flex-col md:flex-row items-center justify-between gap-4 text-xs text-slate-500", children: [_jsx("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-2", children: siteConfig.footer.bottomLinks.map((item, idx) => (_jsx("a", { href: item.href, className: "hover:text-[#012867] transition-colors", children: item.label }, idx))) }), _jsx("div", { children: siteConfig.footer.copyright })] })] }) }));
10
+ }
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ import type { GuideMeta } from "../../types";
3
+ export interface GuideLayoutProps {
4
+ guides: GuideMeta[];
5
+ currentGuide?: GuideMeta;
6
+ children: React.ReactNode;
7
+ }
8
+ export declare function GuideLayout({ guides, currentGuide, children, }: GuideLayoutProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { BookOpen } from "lucide-react";
5
+ export function GuideLayout({ guides, currentGuide, children, }) {
6
+ return (_jsx("div", { className: "min-h-screen pt-28 pb-20 bg-white", children: _jsx("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8", children: _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-12 gap-8", children: [_jsx("aside", { className: "lg:col-span-3 space-y-4", children: _jsxs("div", { className: "p-4 rounded-2xl bg-slate-50 border border-slate-200", children: [_jsxs("div", { className: "flex items-center gap-2 mb-4 text-[#012867] font-bold text-sm", children: [_jsx(BookOpen, { className: "h-4 w-4" }), _jsx("span", { children: "How To Guides" })] }), _jsx("ul", { className: "space-y-1", children: guides.map((g) => {
7
+ const isActive = currentGuide?.slug === g.slug;
8
+ return (_jsx("li", { children: _jsx(Link, { href: `/how-to${g.slug === "/" ? "" : g.slug}`, className: `block px-3 py-2 rounded-xl text-xs font-semibold transition-colors ${isActive
9
+ ? "bg-[#012867] text-white"
10
+ : "text-slate-700 hover:bg-slate-200/70"}`, children: g.title }) }, g.slug));
11
+ }) })] }) }), _jsx("main", { className: "lg:col-span-9", children: children })] }) }) }));
12
+ }
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ export declare function MarkdownContent({ content }: {
3
+ content: string;
4
+ }): React.JSX.Element;
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ export function MarkdownContent({ content }) {
4
+ // Simple markdown renderer for headers, tables, code blocks, and lists
5
+ return (_jsx("div", { className: "prose prose-slate max-w-none text-slate-800 leading-relaxed font-sans", children: _jsx("div", { dangerouslySetInnerHTML: { __html: content } }) }));
6
+ }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import type { HeroConfig } from "../../types";
3
+ export interface HeroSectionProps {
4
+ config?: Partial<HeroConfig>;
5
+ badgeLabel?: string;
6
+ }
7
+ export declare function HeroSection({ config, badgeLabel }: HeroSectionProps): React.JSX.Element;
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useSiteConfig } from "../../context/ConfigContext";
4
+ import { ParticleCanvas } from "./ParticleCanvas";
5
+ import { Button } from "../ui/Button";
6
+ import { Rocket } from "lucide-react";
7
+ export function HeroSection({ config, badgeLabel }) {
8
+ const siteConfig = useSiteConfig();
9
+ const hero = config ? { ...siteConfig.hero, ...config } : siteConfig.hero;
10
+ const pillBadge = badgeLabel || siteConfig.statement.badge || "Modular Application Template";
11
+ return (_jsxs("section", { className: "relative min-h-[90vh] flex flex-col items-center justify-center pt-28 sm:pt-36 pb-16 overflow-hidden", children: [_jsx(ParticleCanvas, { config: hero.particleField }), _jsx("div", { className: "pointer-events-none absolute top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[600px] sm:w-[900px] h-[400px] bg-gradient-to-tr from-[#012867]/15 via-slate-500/10 to-transparent rounded-full blur-3xl opacity-75" }), _jsxs("div", { className: "relative z-10 max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 text-center", children: [_jsxs("div", { className: "inline-flex items-center gap-2 px-3.5 py-1.5 rounded-full bg-[#012867]/[0.06] border border-[#012867]/15 text-xs font-semibold text-[#012867] backdrop-blur-md mb-8 hover:bg-[#012867]/10 transition-colors cursor-default shadow-sm", children: [_jsx(Rocket, { className: "h-3.5 w-3.5 text-[#012867]" }), _jsx("span", { children: pillBadge })] }), _jsx("h1", { className: "text-5xl sm:text-7xl lg:text-8xl font-medium tracking-tight text-slate-900 max-w-4xl mx-auto leading-[1.05]", children: _jsx("span", { className: "text-transparent bg-clip-text bg-gradient-to-r from-[#012867] via-[#01358d] to-[#011f52]", children: hero.headline }) }), _jsx("p", { className: "mt-6 text-base sm:text-lg lg:text-xl text-slate-600 max-w-2xl mx-auto leading-relaxed font-normal", children: hero.subheadline }), _jsxs("div", { className: "mt-8 sm:mt-10 flex flex-wrap items-center justify-center gap-3 sm:gap-4", children: [_jsx(Button, { href: hero.primaryCta.href, variant: "primary", size: "lg", className: "shadow-lg shadow-[#012867]/20 font-bold bg-[#012867] hover:bg-[#011f52] text-white", children: _jsx("span", { children: hero.primaryCta.label }) }), _jsx(Button, { href: hero.secondaryCta.href, variant: "secondary", size: "lg", className: "font-bold border-slate-300 hover:border-slate-400 text-slate-800 bg-slate-50 hover:bg-slate-100", children: _jsx("span", { children: hero.secondaryCta.label }) })] })] })] }));
12
+ }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ import type { ParticleConfig } from "../../types";
3
+ export interface ParticleCanvasProps {
4
+ className?: string;
5
+ config?: ParticleConfig;
6
+ }
7
+ export declare function ParticleCanvas({ className, config, }: ParticleCanvasProps): React.JSX.Element;
@@ -0,0 +1,199 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { useSiteConfig } from "../../context/ConfigContext";
5
+ export function ParticleCanvas({ className = "absolute inset-0 w-full h-full pointer-events-none", config, }) {
6
+ const siteConfig = useSiteConfig();
7
+ const particleField = config || siteConfig.hero.particleField;
8
+ const canvasRef = useRef(null);
9
+ const mouseRef = useRef({ x: -1000, y: -1000, active: false });
10
+ useEffect(() => {
11
+ const canvas = canvasRef.current;
12
+ if (!canvas)
13
+ return;
14
+ const ctx = canvas.getContext("2d");
15
+ if (!ctx)
16
+ return;
17
+ let animationFrameId;
18
+ let width = 0;
19
+ let height = 0;
20
+ let dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
21
+ const particles = [];
22
+ const colors = particleField?.colors || [
23
+ "#012867",
24
+ "#01358d",
25
+ "#F97316",
26
+ "#64748B",
27
+ "#38BDF8",
28
+ ];
29
+ const initParticles = (targetCount, w, h) => {
30
+ particles.length = 0;
31
+ for (let i = 0; i < targetCount; i++) {
32
+ const color = colors[Math.floor(Math.random() * colors.length)];
33
+ const maxSpeed = particleField?.maxSpeed || 0.45;
34
+ const isSparkle = Math.random() > 0.65;
35
+ particles.push({
36
+ x: Math.random() * w,
37
+ y: Math.random() * h,
38
+ vx: (Math.random() - 0.5) * maxSpeed,
39
+ vy: (Math.random() - 0.5) * maxSpeed,
40
+ radius: isSparkle ? Math.random() * 2.5 + 1.2 : Math.random() * 1.8 + 0.8,
41
+ color,
42
+ baseAlpha: Math.random() * 0.35 + 0.25,
43
+ alpha: Math.random() * 0.35 + 0.25,
44
+ phase: Math.random() * Math.PI * 2,
45
+ twinkleSpeed: Math.random() * 0.03 + 0.015,
46
+ isSparkle,
47
+ });
48
+ }
49
+ };
50
+ const resizeCanvas = () => {
51
+ if (!canvas || !ctx)
52
+ return;
53
+ const rect = canvas.getBoundingClientRect ? canvas.getBoundingClientRect() : { width: 1200, height: 800 };
54
+ const newWidth = Math.max(rect.width || 0, typeof window !== "undefined" ? window.innerWidth : 1200, 300);
55
+ const newHeight = Math.max(rect.height || 0, canvas.parentElement?.offsetHeight || 600, 300);
56
+ dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
57
+ canvas.width = Math.round(newWidth * dpr);
58
+ canvas.height = Math.round(newHeight * dpr);
59
+ canvas.style.width = `${newWidth}px`;
60
+ canvas.style.height = `${newHeight}px`;
61
+ if (typeof ctx.setTransform === "function") {
62
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
63
+ }
64
+ if (typeof ctx.scale === "function") {
65
+ ctx.scale(dpr, dpr);
66
+ }
67
+ if (particles.length === 0) {
68
+ const baseCount = particleField?.count || 85;
69
+ const calculatedCount = Math.min(baseCount * 1.5, Math.max(40, Math.floor((newWidth * newHeight) / 12000)));
70
+ initParticles(calculatedCount, newWidth, newHeight);
71
+ }
72
+ else if (width > 0 && height > 0) {
73
+ const scaleX = newWidth / width;
74
+ const scaleY = newHeight / height;
75
+ for (const p of particles) {
76
+ p.x *= scaleX;
77
+ p.y *= scaleY;
78
+ }
79
+ }
80
+ width = newWidth;
81
+ height = newHeight;
82
+ };
83
+ resizeCanvas();
84
+ let resizeObserver = null;
85
+ if (typeof ResizeObserver !== "undefined") {
86
+ resizeObserver = new ResizeObserver(() => {
87
+ resizeCanvas();
88
+ });
89
+ if (canvas.parentElement) {
90
+ resizeObserver.observe(canvas.parentElement);
91
+ }
92
+ resizeObserver.observe(canvas);
93
+ }
94
+ window.addEventListener("resize", resizeCanvas);
95
+ const handleMouseMove = (e) => {
96
+ if (!canvas)
97
+ return;
98
+ const rect = canvas.getBoundingClientRect ? canvas.getBoundingClientRect() : { left: 0, top: 0 };
99
+ mouseRef.current = {
100
+ x: e.clientX - rect.left,
101
+ y: e.clientY - rect.top,
102
+ active: true,
103
+ };
104
+ };
105
+ const handleMouseLeave = () => {
106
+ mouseRef.current.active = false;
107
+ mouseRef.current.x = -1000;
108
+ mouseRef.current.y = -1000;
109
+ };
110
+ window.addEventListener("mousemove", handleMouseMove, { passive: true });
111
+ document.addEventListener("mouseleave", handleMouseLeave);
112
+ const maxDistance = particleField?.connectionDistance || 140;
113
+ const render = () => {
114
+ if (!ctx || width === 0 || height === 0) {
115
+ animationFrameId = requestAnimationFrame(render);
116
+ return;
117
+ }
118
+ if (typeof ctx.clearRect === "function") {
119
+ ctx.clearRect(0, 0, width, height);
120
+ }
121
+ for (let i = 0; i < particles.length; i++) {
122
+ const p = particles[i];
123
+ p.x += p.vx;
124
+ p.y += p.vy;
125
+ p.phase += p.twinkleSpeed;
126
+ const oscillation = Math.sin(p.phase);
127
+ p.alpha = Math.max(0.1, Math.min(0.9, p.baseAlpha + oscillation * 0.3));
128
+ if (p.x < -30)
129
+ p.x = width + 30;
130
+ if (p.x > width + 30)
131
+ p.x = -30;
132
+ if (p.y < -30)
133
+ p.y = height + 30;
134
+ if (p.y > height + 30)
135
+ p.y = -30;
136
+ if (mouseRef.current.active) {
137
+ const dx = p.x - mouseRef.current.x;
138
+ const dy = p.y - mouseRef.current.y;
139
+ const dist = Math.sqrt(dx * dx + dy * dy);
140
+ const repelRadius = 160;
141
+ if (dist < repelRadius && dist > 0.01) {
142
+ const force = (repelRadius - dist) / repelRadius;
143
+ const angle = Math.atan2(dy, dx);
144
+ p.x += Math.cos(angle) * force * 3.5;
145
+ p.y += Math.sin(angle) * force * 3.5;
146
+ }
147
+ }
148
+ if (typeof ctx.save === "function") {
149
+ ctx.save();
150
+ ctx.beginPath();
151
+ ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
152
+ ctx.fillStyle = p.color;
153
+ ctx.globalAlpha = p.alpha;
154
+ ctx.fill();
155
+ if (p.isSparkle && oscillation > 0.5) {
156
+ const flareLen = p.radius * (1.5 + oscillation * 1.5);
157
+ ctx.beginPath();
158
+ ctx.moveTo(p.x - flareLen, p.y);
159
+ ctx.lineTo(p.x + flareLen, p.y);
160
+ ctx.moveTo(p.x, p.y - flareLen);
161
+ ctx.lineTo(p.x + flareLen, p.y);
162
+ ctx.strokeStyle = p.color;
163
+ ctx.lineWidth = 0.75;
164
+ ctx.globalAlpha = (oscillation - 0.5) * 0.8;
165
+ ctx.stroke();
166
+ }
167
+ ctx.restore();
168
+ }
169
+ for (let j = i + 1; j < particles.length; j++) {
170
+ const p2 = particles[j];
171
+ const dx = p.x - p2.x;
172
+ const dy = p.y - p2.y;
173
+ const dist = Math.sqrt(dx * dx + dy * dy);
174
+ if (dist < maxDistance && typeof ctx.beginPath === "function") {
175
+ ctx.beginPath();
176
+ ctx.moveTo(p.x, p.y);
177
+ ctx.lineTo(p2.x, p2.y);
178
+ ctx.strokeStyle = p.color;
179
+ ctx.globalAlpha = (1 - dist / maxDistance) * 0.12 * Math.min(p.alpha, p2.alpha);
180
+ ctx.lineWidth = 0.6;
181
+ ctx.stroke();
182
+ }
183
+ }
184
+ }
185
+ animationFrameId = requestAnimationFrame(render);
186
+ };
187
+ render();
188
+ return () => {
189
+ if (resizeObserver) {
190
+ resizeObserver.disconnect();
191
+ }
192
+ window.removeEventListener("resize", resizeCanvas);
193
+ window.removeEventListener("mousemove", handleMouseMove);
194
+ document.removeEventListener("mouseleave", handleMouseLeave);
195
+ cancelAnimationFrame(animationFrameId);
196
+ };
197
+ }, [particleField]);
198
+ return (_jsx("canvas", { ref: canvasRef, className: className, style: { display: "block", position: "absolute", top: 0, left: 0, width: "100%", height: "100%" } }));
199
+ }
@@ -0,0 +1,11 @@
1
+ import React from "react";
2
+ export interface VideoModalProps {
3
+ isOpen: boolean;
4
+ onClose: () => void;
5
+ videoUrl: string;
6
+ title?: string;
7
+ badge?: string;
8
+ orientation?: "landscape" | "portrait";
9
+ aspectRatio?: "16/9" | "9/16" | "landscape" | "portrait";
10
+ }
11
+ export declare function VideoModal({ isOpen, onClose, videoUrl, title, badge, orientation, aspectRatio, }: VideoModalProps): React.JSX.Element | null;
@@ -0,0 +1,42 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useSyncExternalStore } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { X, Smartphone, Monitor } from "lucide-react";
6
+ const emptySubscribe = () => () => { };
7
+ export function VideoModal({ isOpen, onClose, videoUrl, title = "Video Preview", badge, orientation, aspectRatio, }) {
8
+ const mounted = useSyncExternalStore(emptySubscribe, () => true, () => false);
9
+ useEffect(() => {
10
+ const handleKeyDown = (e) => {
11
+ if (e.key === "Escape")
12
+ onClose();
13
+ };
14
+ if (isOpen) {
15
+ const originalOverflow = document.body.style.overflow;
16
+ document.body.style.overflow = "hidden";
17
+ window.addEventListener("keydown", handleKeyDown);
18
+ return () => {
19
+ document.body.style.overflow = originalOverflow;
20
+ window.removeEventListener("keydown", handleKeyDown);
21
+ };
22
+ }
23
+ }, [isOpen, onClose]);
24
+ if (!isOpen)
25
+ return null;
26
+ const isPortrait = orientation === "portrait" ||
27
+ aspectRatio === "9/16" ||
28
+ aspectRatio === "portrait" ||
29
+ videoUrl.toLowerCase().includes("portrait") ||
30
+ videoUrl.toLowerCase().includes("echopay.mp4") ||
31
+ videoUrl.toLowerCase().includes("two.mp4") ||
32
+ videoUrl.toLowerCase().includes("builders-merchant");
33
+ const isLocalMp4 = videoUrl.endsWith(".mp4") ||
34
+ videoUrl.startsWith("/mp4/") ||
35
+ (!videoUrl.startsWith("http://") && !videoUrl.startsWith("https://") && !videoUrl.includes("embed"));
36
+ const modalContent = (_jsxs("div", { "data-testid": "video-modal-overlay", className: "fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-6 md:p-8 animate-in fade-in duration-200", children: [_jsx("div", { className: "fixed inset-0 bg-black/85 backdrop-blur-xl transition-opacity", onClick: onClose, "aria-hidden": "true", "data-testid": "video-modal-backdrop" }), _jsxs("div", { className: `relative z-10 w-full overflow-hidden rounded-3xl bg-[#111317] border border-white/15 shadow-2xl animate-in zoom-in-95 duration-200 flex flex-col ${isPortrait
37
+ ? "max-w-[340px] sm:max-w-[380px] max-h-[90vh]"
38
+ : "max-w-4xl sm:max-w-5xl"}`, children: [_jsxs("div", { className: "flex items-center justify-between px-4 sm:px-6 py-3.5 border-b border-white/10 bg-[#16181e]/90 backdrop-blur-md shrink-0", children: [_jsxs("div", { className: "flex items-center gap-2 min-w-0 pr-2", children: [_jsx("span", { className: "h-2.5 w-2.5 rounded-full bg-slate-400 animate-pulse shrink-0" }), _jsxs("div", { className: "flex items-center gap-2 truncate", children: [isPortrait ? (_jsx(Smartphone, { className: "h-3.5 w-3.5 text-slate-400 shrink-0" })) : (_jsx(Monitor, { className: "h-3.5 w-3.5 text-[#38BDF8] shrink-0" })), _jsx("h3", { className: "text-xs sm:text-sm font-semibold text-neutral-200 truncate", children: title })] }), badge && (_jsx("span", { className: "hidden sm:inline-flex items-center px-2 py-0.5 rounded-full text-[10px] font-bold bg-white/10 text-slate-300 border border-white/10 shrink-0", children: badge }))] }), _jsx("button", { onClick: onClose, className: "rounded-full p-1.5 text-neutral-400 hover:text-white hover:bg-white/10 transition-colors shrink-0 cursor-pointer", "aria-label": "Close modal", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsx("div", { className: `relative w-full bg-black flex items-center justify-center overflow-hidden ${isPortrait ? "aspect-[9/16] max-h-[75vh]" : "aspect-video"}`, children: isLocalMp4 ? (_jsx("video", { src: videoUrl, title: title, controls: true, autoPlay: true, playsInline: true, className: "w-full h-full object-contain bg-black", "data-testid": "local-video-player" })) : (_jsx("iframe", { src: videoUrl, title: title, className: "absolute inset-0 h-full w-full border-0", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", allowFullScreen: true })) })] })] }));
39
+ return mounted && typeof document !== "undefined"
40
+ ? createPortal(modalContent, document.body)
41
+ : modalContent;
42
+ }
@@ -0,0 +1,23 @@
1
+ export * from "./ui/Button";
2
+ export * from "./ui/Card";
3
+ export * from "./ui/CustomCursor";
4
+ export * from "./ui/ThemeToggle";
5
+ export * from "./hero/ParticleCanvas";
6
+ export * from "./hero/VideoModal";
7
+ export * from "./hero/HeroSection";
8
+ export * from "./navigation/DropdownMenu";
9
+ export * from "./navigation/MobileMenu";
10
+ export * from "./navigation/ShareMenu";
11
+ export * from "./navigation/LogoContextMenu";
12
+ export * from "./navigation/Header";
13
+ export * from "./footer/Footer";
14
+ export * from "./showcase/FeatureExplorer";
15
+ export * from "./showcase/SolutionsSection";
16
+ export * from "./showcase/StatementSection";
17
+ export * from "./showcase/UseCaseSlider";
18
+ export * from "./showcase/VideoSection";
19
+ export * from "./showcase/BlogSection";
20
+ export * from "./download/DownloadBanner";
21
+ export * from "./guide/MarkdownContent";
22
+ export * from "./guide/GuideLayout";
23
+ export * from "./video/VideoLayout";
@@ -0,0 +1,23 @@
1
+ export * from "./ui/Button";
2
+ export * from "./ui/Card";
3
+ export * from "./ui/CustomCursor";
4
+ export * from "./ui/ThemeToggle";
5
+ export * from "./hero/ParticleCanvas";
6
+ export * from "./hero/VideoModal";
7
+ export * from "./hero/HeroSection";
8
+ export * from "./navigation/DropdownMenu";
9
+ export * from "./navigation/MobileMenu";
10
+ export * from "./navigation/ShareMenu";
11
+ export * from "./navigation/LogoContextMenu";
12
+ export * from "./navigation/Header";
13
+ export * from "./footer/Footer";
14
+ export * from "./showcase/FeatureExplorer";
15
+ export * from "./showcase/SolutionsSection";
16
+ export * from "./showcase/StatementSection";
17
+ export * from "./showcase/UseCaseSlider";
18
+ export * from "./showcase/VideoSection";
19
+ export * from "./showcase/BlogSection";
20
+ export * from "./download/DownloadBanner";
21
+ export * from "./guide/MarkdownContent";
22
+ export * from "./guide/GuideLayout";
23
+ export * from "./video/VideoLayout";
@@ -0,0 +1,6 @@
1
+ import React from "react";
2
+ import type { NavItem } from "../../types";
3
+ export interface DropdownMenuProps {
4
+ item: NavItem;
5
+ }
6
+ export declare function DropdownMenu({ item }: DropdownMenuProps): React.JSX.Element;
@@ -0,0 +1,67 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import Link from "next/link";
5
+ import { usePathname } from "next/navigation";
6
+ import { ChevronDown, Layers, Terminal, Code2, Cpu, Globe, Layout, Sparkles, Building2, BookOpen, Newspaper, History, LifeBuoy, ShieldCheck, Zap, Play, Video, ExternalLink, Download, Settings, } from "lucide-react";
7
+ import { cn } from "../../lib/utils";
8
+ const iconMap = {
9
+ Layers,
10
+ Terminal,
11
+ Code2,
12
+ Cpu,
13
+ Globe,
14
+ Layout,
15
+ Sparkles,
16
+ Building2,
17
+ BookOpen,
18
+ Newspaper,
19
+ History,
20
+ LifeBuoy,
21
+ ShieldCheck,
22
+ Zap,
23
+ Play,
24
+ Video,
25
+ Download,
26
+ Settings,
27
+ };
28
+ export function DropdownMenu({ item }) {
29
+ const [isOpen, setIsOpen] = React.useState(false);
30
+ const timeoutRef = React.useRef(null);
31
+ const handleMouseEnter = () => {
32
+ if (timeoutRef.current)
33
+ clearTimeout(timeoutRef.current);
34
+ setIsOpen(true);
35
+ };
36
+ const handleMouseLeave = () => {
37
+ timeoutRef.current = setTimeout(() => {
38
+ setIsOpen(false);
39
+ }, 150);
40
+ };
41
+ const dropdownItems = (item.dropdown || []);
42
+ const rawPathname = usePathname();
43
+ const pathname = rawPathname || "";
44
+ const isSectionActive = Boolean(item.href && item.href !== "#" && pathname && pathname.startsWith(item.href)) ||
45
+ dropdownItems.some((d) => Boolean(d.href && pathname && pathname === d.href));
46
+ return (_jsxs("div", { className: "relative", onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, children: [_jsxs("button", { type: "button", onClick: () => setIsOpen(!isOpen), className: cn("flex items-center gap-1 px-3.5 py-1.5 text-sm font-medium transition-colors rounded-full cursor-pointer", isOpen
47
+ ? "text-slate-900 bg-slate-100"
48
+ : isSectionActive
49
+ ? "text-[#012867] bg-[#012867]/[0.06] font-semibold"
50
+ : "text-slate-600 hover:text-[#012867] hover:bg-slate-100/80"), children: [_jsx("span", { children: item.label }), _jsx(ChevronDown, { className: cn("h-3.5 w-3.5 transition-transform duration-200 opacity-70", isOpen && "rotate-180") })] }), isOpen && (_jsx("div", { className: "absolute left-1/2 -translate-x-1/2 top-full pt-2 z-50 w-72 md:w-80", children: _jsx("div", { className: "rounded-2xl bg-white border border-slate-200 p-2 shadow-2xl backdrop-blur-2xl animate-in fade-in zoom-in-95 duration-150", children: _jsx("div", { className: "space-y-1", children: dropdownItems.map((dropdownItem, idx) => {
51
+ const IconComponent = dropdownItem.icon
52
+ ? iconMap[dropdownItem.icon]
53
+ : null;
54
+ const isActive = pathname === dropdownItem.href;
55
+ const content = (_jsxs("div", { className: cn("flex items-start gap-3 rounded-xl p-2.5 transition-colors group", isActive
56
+ ? "bg-[#012867]/[0.08] text-[#012867]"
57
+ : "hover:bg-[#012867]/[0.05]"), children: [IconComponent && (_jsx("div", { className: cn("mt-0.5 rounded-lg p-1.5 transition-colors", isActive
58
+ ? "bg-[#012867] text-white"
59
+ : "bg-slate-100 text-slate-600 group-hover:bg-[#012867]/10 group-hover:text-[#012867]"), children: _jsx(IconComponent, { className: "h-4 w-4" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx("span", { className: cn("text-xs font-bold transition-colors", isActive
60
+ ? "text-[#012867]"
61
+ : "text-slate-900 group-hover:text-[#012867]"), children: dropdownItem.title }), dropdownItem.badge && (_jsx("span", { className: "rounded-full bg-[#012867]/[0.06] px-1.5 py-0.2 text-[10px] font-bold text-[#012867] border border-[#012867]/15", children: dropdownItem.badge })), dropdownItem.external && (_jsx(ExternalLink, { className: "h-3 w-3 text-slate-400 group-hover:text-[#012867]" }))] }), dropdownItem.description && (_jsx("p", { className: "text-[11px] text-slate-500 line-clamp-2 mt-0.5 font-normal", children: dropdownItem.description }))] })] }));
62
+ if (dropdownItem.external) {
63
+ return (_jsx("a", { href: dropdownItem.href, target: "_blank", rel: "noopener noreferrer", className: "block", onClick: () => setIsOpen(false), children: content }, idx));
64
+ }
65
+ return (_jsx(Link, { href: dropdownItem.href, className: "block", onClick: () => setIsOpen(false), children: content }, idx));
66
+ }) }) }) }))] }));
67
+ }
@@ -0,0 +1,5 @@
1
+ import React from "react";
2
+ export interface HeaderProps {
3
+ className?: string;
4
+ }
5
+ export declare function Header({ className }: HeaderProps): React.JSX.Element;
@@ -0,0 +1,32 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import Link from "next/link";
5
+ import { LayoutDashboard, ExternalLink } from "lucide-react";
6
+ import { useSiteConfig } from "../../context/ConfigContext";
7
+ import { LogoContextMenu } from "./LogoContextMenu";
8
+ import { DropdownMenu } from "./DropdownMenu";
9
+ import { MobileMenu } from "./MobileMenu";
10
+ import { ShareMenu } from "./ShareMenu";
11
+ import { Button } from "../ui/Button";
12
+ import { cn } from "../../lib/utils";
13
+ export function Header({ className }) {
14
+ const siteConfig = useSiteConfig();
15
+ const [scrolled, setScrolled] = useState(false);
16
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
17
+ useEffect(() => {
18
+ const handleScroll = () => {
19
+ setScrolled(window.scrollY > 20);
20
+ };
21
+ window.addEventListener("scroll", handleScroll, { passive: true });
22
+ return () => window.removeEventListener("scroll", handleScroll);
23
+ }, []);
24
+ return (_jsx("header", { className: cn("fixed top-0 inset-x-0 z-50 transition-all duration-300", scrolled || mobileMenuOpen
25
+ ? "bg-white/95 backdrop-blur-xl border-b border-slate-200/80 shadow-sm py-2.5"
26
+ : "bg-transparent py-4", className), children: _jsxs("div", { className: "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-8", children: [_jsx(LogoContextMenu, {}), _jsx("nav", { className: "hidden md:flex items-center gap-1", children: siteConfig.navigation.links.map((item, idx) => {
27
+ if (item.dropdown && item.dropdown.length > 0) {
28
+ return _jsx(DropdownMenu, { item: item }, idx);
29
+ }
30
+ return (_jsx(Link, { href: item.href || "#", target: item.external ? "_blank" : undefined, rel: item.external ? "noreferrer" : undefined, className: "px-3.5 py-1.5 text-sm font-medium text-slate-600 hover:text-[#012867] hover:bg-slate-100/80 rounded-full transition-colors", children: item.label }, idx));
31
+ }) })] }), _jsxs("div", { className: "flex items-center gap-2.5 sm:gap-3", children: [_jsx(ShareMenu, {}), siteConfig.navigation.remoteControlBadge?.enabled && (_jsxs("a", { href: siteConfig.navigation.remoteControlBadge.href, target: "_blank", rel: "noopener noreferrer", title: siteConfig.navigation.remoteControlBadge.tooltip, className: "hidden sm:inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-semibold text-slate-700 bg-slate-100/80 hover:bg-[#012867]/[0.08] hover:text-[#012867] border border-slate-200/80 transition-colors", children: [_jsx(LayoutDashboard, { className: "h-3.5 w-3.5 text-slate-500" }), _jsx("span", { children: siteConfig.navigation.remoteControlBadge.label }), _jsx(ExternalLink, { className: "h-3 w-3 text-slate-400" })] })), _jsx(Button, { href: siteConfig.navigation.primaryCta.href, variant: "primary", size: "sm", icon: siteConfig.navigation.primaryCta.icon, iconPosition: "right", className: "shadow-sm shadow-[#012867]/20 font-bold bg-[#012867] hover:bg-[#011f52] text-white", children: _jsx("span", { children: siteConfig.navigation.primaryCta.label }) }), _jsx(MobileMenu, { isOpen: mobileMenuOpen, onToggle: () => setMobileMenuOpen((prev) => !prev), onClose: () => setMobileMenuOpen(false) })] })] }) }));
32
+ }
@@ -0,0 +1,7 @@
1
+ import React from "react";
2
+ export declare function BrandLogo({ className }: {
3
+ className?: string;
4
+ }): React.JSX.Element;
5
+ export declare const EchoPayLogo: typeof BrandLogo;
6
+ export declare const NxLogo: typeof BrandLogo;
7
+ export declare function LogoContextMenu(): React.JSX.Element;