@opensite/ui 0.1.3 → 0.1.4
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/components.cjs +1501 -0
- package/dist/components.cjs.map +1 -1
- package/dist/components.d.cts +13 -0
- package/dist/components.d.ts +13 -0
- package/dist/components.js +1489 -1
- package/dist/components.js.map +1 -1
- package/dist/footer-animated-social.cjs +272 -0
- package/dist/footer-animated-social.cjs.map +1 -0
- package/dist/footer-animated-social.d.cts +41 -0
- package/dist/footer-animated-social.d.ts +41 -0
- package/dist/footer-animated-social.js +250 -0
- package/dist/footer-animated-social.js.map +1 -0
- package/dist/footer-background-card.cjs +149 -0
- package/dist/footer-background-card.cjs.map +1 -0
- package/dist/footer-background-card.d.cts +74 -0
- package/dist/footer-background-card.d.ts +74 -0
- package/dist/footer-background-card.js +147 -0
- package/dist/footer-background-card.js.map +1 -0
- package/dist/footer-brand-description.cjs +244 -0
- package/dist/footer-brand-description.cjs.map +1 -0
- package/dist/footer-brand-description.d.cts +65 -0
- package/dist/footer-brand-description.d.ts +65 -0
- package/dist/footer-brand-description.js +222 -0
- package/dist/footer-brand-description.js.map +1 -0
- package/dist/footer-contact-card.cjs +238 -0
- package/dist/footer-contact-card.cjs.map +1 -0
- package/dist/footer-contact-card.d.cts +65 -0
- package/dist/footer-contact-card.d.ts +65 -0
- package/dist/footer-contact-card.js +216 -0
- package/dist/footer-contact-card.js.map +1 -0
- package/dist/footer-cta-banner.cjs +282 -0
- package/dist/footer-cta-banner.cjs.map +1 -0
- package/dist/footer-cta-banner.d.cts +77 -0
- package/dist/footer-cta-banner.d.ts +77 -0
- package/dist/footer-cta-banner.js +260 -0
- package/dist/footer-cta-banner.js.map +1 -0
- package/dist/footer-cta-social.cjs +221 -0
- package/dist/footer-cta-social.cjs.map +1 -0
- package/dist/footer-cta-social.d.cts +45 -0
- package/dist/footer-cta-social.d.ts +45 -0
- package/dist/footer-cta-social.js +199 -0
- package/dist/footer-cta-social.js.map +1 -0
- package/dist/footer-links-grid.cjs +119 -0
- package/dist/footer-links-grid.cjs.map +1 -0
- package/dist/footer-links-grid.d.cts +54 -0
- package/dist/footer-links-grid.d.ts +54 -0
- package/dist/footer-links-grid.js +117 -0
- package/dist/footer-links-grid.js.map +1 -0
- package/dist/footer-nav-social.cjs +273 -0
- package/dist/footer-nav-social.cjs.map +1 -0
- package/dist/footer-nav-social.d.cts +72 -0
- package/dist/footer-nav-social.d.ts +72 -0
- package/dist/footer-nav-social.js +251 -0
- package/dist/footer-nav-social.js.map +1 -0
- package/dist/footer-newsletter-grid.cjs +271 -0
- package/dist/footer-newsletter-grid.cjs.map +1 -0
- package/dist/footer-newsletter-grid.d.cts +74 -0
- package/dist/footer-newsletter-grid.d.ts +74 -0
- package/dist/footer-newsletter-grid.js +249 -0
- package/dist/footer-newsletter-grid.js.map +1 -0
- package/dist/footer-newsletter-minimal.cjs +271 -0
- package/dist/footer-newsletter-minimal.cjs.map +1 -0
- package/dist/footer-newsletter-minimal.d.cts +57 -0
- package/dist/footer-newsletter-minimal.d.ts +57 -0
- package/dist/footer-newsletter-minimal.js +249 -0
- package/dist/footer-newsletter-minimal.js.map +1 -0
- package/dist/footer-simple-centered.cjs +101 -0
- package/dist/footer-simple-centered.cjs.map +1 -0
- package/dist/footer-simple-centered.d.cts +52 -0
- package/dist/footer-simple-centered.d.ts +52 -0
- package/dist/footer-simple-centered.js +99 -0
- package/dist/footer-simple-centered.js.map +1 -0
- package/dist/footer-social-apps.cjs +247 -0
- package/dist/footer-social-apps.cjs.map +1 -0
- package/dist/footer-social-apps.d.cts +75 -0
- package/dist/footer-social-apps.d.ts +75 -0
- package/dist/footer-social-apps.js +225 -0
- package/dist/footer-social-apps.js.map +1 -0
- package/dist/footer-social-newsletter.cjs +267 -0
- package/dist/footer-social-newsletter.cjs.map +1 -0
- package/dist/footer-social-newsletter.d.cts +68 -0
- package/dist/footer-social-newsletter.d.ts +68 -0
- package/dist/footer-social-newsletter.js +245 -0
- package/dist/footer-social-newsletter.js.map +1 -0
- package/dist/index.cjs +1501 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +1489 -1
- package/dist/index.js.map +1 -1
- package/dist/registry.cjs +1971 -1
- package/dist/registry.cjs.map +1 -1
- package/dist/registry.js +1971 -1
- package/dist/registry.js.map +1 -1
- package/dist/team-media-showcase.cjs +182 -0
- package/dist/team-media-showcase.cjs.map +1 -0
- package/dist/team-media-showcase.d.cts +145 -0
- package/dist/team-media-showcase.d.ts +145 -0
- package/dist/team-media-showcase.js +160 -0
- package/dist/team-media-showcase.js.map +1 -0
- package/package.json +71 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var clsx = require('clsx');
|
|
6
|
+
var tailwindMerge = require('tailwind-merge');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
function _interopNamespace(e) {
|
|
10
|
+
if (e && e.__esModule) return e;
|
|
11
|
+
var n = Object.create(null);
|
|
12
|
+
if (e) {
|
|
13
|
+
Object.keys(e).forEach(function (k) {
|
|
14
|
+
if (k !== 'default') {
|
|
15
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
16
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
get: function () { return e[k]; }
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
n.default = e;
|
|
24
|
+
return Object.freeze(n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
28
|
+
|
|
29
|
+
// components/blocks/footers/footer-cta-social.tsx
|
|
30
|
+
function cn(...inputs) {
|
|
31
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
32
|
+
}
|
|
33
|
+
var svgCache = /* @__PURE__ */ new Map();
|
|
34
|
+
function DynamicIcon({
|
|
35
|
+
name,
|
|
36
|
+
size = 28,
|
|
37
|
+
color,
|
|
38
|
+
className,
|
|
39
|
+
alt
|
|
40
|
+
}) {
|
|
41
|
+
const [svgContent, setSvgContent] = React__namespace.useState(null);
|
|
42
|
+
const [isLoading, setIsLoading] = React__namespace.useState(true);
|
|
43
|
+
const [error, setError] = React__namespace.useState(null);
|
|
44
|
+
const { url, iconName } = React__namespace.useMemo(() => {
|
|
45
|
+
const separator = name.includes("/") ? "/" : ":";
|
|
46
|
+
const [prefix, iconName2] = name.split(separator);
|
|
47
|
+
const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
|
|
48
|
+
return {
|
|
49
|
+
url: baseUrl,
|
|
50
|
+
iconName: iconName2
|
|
51
|
+
};
|
|
52
|
+
}, [name, size]);
|
|
53
|
+
React__namespace.useEffect(() => {
|
|
54
|
+
let isMounted = true;
|
|
55
|
+
const fetchSvg = async () => {
|
|
56
|
+
const cached = svgCache.get(url);
|
|
57
|
+
if (cached) {
|
|
58
|
+
if (isMounted) {
|
|
59
|
+
setSvgContent(cached);
|
|
60
|
+
setIsLoading(false);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
setIsLoading(true);
|
|
66
|
+
setError(null);
|
|
67
|
+
const response = await fetch(url);
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`Failed to fetch icon: ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
let svg = await response.text();
|
|
72
|
+
svg = processSvgForCurrentColor(svg);
|
|
73
|
+
svgCache.set(url, svg);
|
|
74
|
+
if (isMounted) {
|
|
75
|
+
setSvgContent(svg);
|
|
76
|
+
setIsLoading(false);
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (isMounted) {
|
|
80
|
+
setError(err instanceof Error ? err.message : "Failed to load icon");
|
|
81
|
+
setIsLoading(false);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
fetchSvg();
|
|
86
|
+
return () => {
|
|
87
|
+
isMounted = false;
|
|
88
|
+
};
|
|
89
|
+
}, [url]);
|
|
90
|
+
if (isLoading) {
|
|
91
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
92
|
+
"span",
|
|
93
|
+
{
|
|
94
|
+
className: cn("inline-block", className),
|
|
95
|
+
style: { width: size, height: size },
|
|
96
|
+
"aria-hidden": "true"
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (error || !svgContent) {
|
|
101
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
102
|
+
"span",
|
|
103
|
+
{
|
|
104
|
+
className: cn("inline-block", className),
|
|
105
|
+
style: { width: size, height: size },
|
|
106
|
+
role: "img",
|
|
107
|
+
"aria-label": alt || iconName
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
112
|
+
"span",
|
|
113
|
+
{
|
|
114
|
+
className: cn("inline-flex items-center justify-center", className),
|
|
115
|
+
style: {
|
|
116
|
+
width: size,
|
|
117
|
+
height: size,
|
|
118
|
+
color: color || "inherit"
|
|
119
|
+
},
|
|
120
|
+
role: "img",
|
|
121
|
+
"aria-label": alt || iconName,
|
|
122
|
+
dangerouslySetInnerHTML: { __html: svgContent }
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
function processSvgForCurrentColor(svg) {
|
|
127
|
+
let processed = svg;
|
|
128
|
+
processed = processed.replace(
|
|
129
|
+
/stroke=["'](#000000|#000|black)["']/gi,
|
|
130
|
+
'stroke="currentColor"'
|
|
131
|
+
);
|
|
132
|
+
processed = processed.replace(
|
|
133
|
+
/fill=["'](#000000|#000|black)["']/gi,
|
|
134
|
+
'fill="currentColor"'
|
|
135
|
+
);
|
|
136
|
+
return processed;
|
|
137
|
+
}
|
|
138
|
+
var defaultSocialLinks = [
|
|
139
|
+
{ icon: "lucide/twitter", url: "#", label: "Twitter" },
|
|
140
|
+
{ icon: "lucide/instagram", url: "#", label: "Instagram" },
|
|
141
|
+
{ icon: "lucide/facebook", url: "#", label: "Facebook" }
|
|
142
|
+
];
|
|
143
|
+
function FooterCtaSocial({
|
|
144
|
+
className,
|
|
145
|
+
preHeading = "Let's connect",
|
|
146
|
+
heading = "You want to scale faster? Try Opensite today.",
|
|
147
|
+
description = "Join thousands of companies already using our platform to scale their operations",
|
|
148
|
+
buttonText = "Get Started Now",
|
|
149
|
+
buttonUrl = "#",
|
|
150
|
+
email = "hello@opensite.ai",
|
|
151
|
+
socialLinks = defaultSocialLinks
|
|
152
|
+
}) {
|
|
153
|
+
return /* @__PURE__ */ jsxRuntime.jsx("section", { className: cn("relative py-32", className), children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto flex max-w-3xl flex-col items-center gap-2 text-center", children: [
|
|
154
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center gap-4", children: [
|
|
155
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-[linear-gradient(270deg,var(--primary,rgb(255,255,255))_0%,var(--secondary,rgb(0,0,0))_100%)] opacity-50" }),
|
|
156
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground italic md:text-base", children: preHeading }),
|
|
157
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px flex-1 bg-[linear-gradient(270deg,var(--secondary,rgb(0,0,0))_0%,var(--primary,rgb(255,255,255))_100%)] opacity-50" })
|
|
158
|
+
] }),
|
|
159
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "py-6 text-5xl font-bold md:text-6xl", children: heading }),
|
|
160
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "max-w-2xl text-base text-muted-foreground md:text-lg", children: description }),
|
|
161
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
162
|
+
"a",
|
|
163
|
+
{
|
|
164
|
+
href: buttonUrl,
|
|
165
|
+
className: "group relative mt-4 inline-flex items-center gap-2 rounded-lg border bg-background px-8 py-4 text-base font-medium transition-all hover:bg-muted",
|
|
166
|
+
children: [
|
|
167
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: buttonText }),
|
|
168
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
169
|
+
DynamicIcon,
|
|
170
|
+
{
|
|
171
|
+
name: "lucide/arrow-up-right",
|
|
172
|
+
size: 16,
|
|
173
|
+
className: "transition-transform group-hover:translate-x-1 group-hover:-translate-y-1"
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
),
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-6 pt-8", children: socialLinks.map((link, idx) => /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
181
|
+
"a",
|
|
182
|
+
{
|
|
183
|
+
href: link.url,
|
|
184
|
+
className: "text-muted-foreground transition-colors hover:text-foreground",
|
|
185
|
+
"aria-label": link.label,
|
|
186
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: link.icon, size: 20 })
|
|
187
|
+
}
|
|
188
|
+
),
|
|
189
|
+
idx < socialLinks.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-4 w-px bg-border" })
|
|
190
|
+
] }, idx)) }),
|
|
191
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "pt-2 text-sm text-muted-foreground md:text-base", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
192
|
+
"a",
|
|
193
|
+
{
|
|
194
|
+
href: `mailto:${email}`,
|
|
195
|
+
className: "transition-colors hover:text-foreground",
|
|
196
|
+
children: email
|
|
197
|
+
}
|
|
198
|
+
) }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-8 border-t pt-8 text-sm text-muted-foreground", children: [
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
|
|
201
|
+
"\xA9 ",
|
|
202
|
+
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
203
|
+
" Opensite AI. All rights reserved."
|
|
204
|
+
] }),
|
|
205
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
206
|
+
"a",
|
|
207
|
+
{
|
|
208
|
+
href: "https://opensite.ai",
|
|
209
|
+
className: "mt-2 inline-block hover:text-foreground",
|
|
210
|
+
target: "_blank",
|
|
211
|
+
rel: "noopener noreferrer",
|
|
212
|
+
children: "AI Website and Automation Platform by Opensite"
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
] })
|
|
216
|
+
] }) }) });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
exports.FooterCtaSocial = FooterCtaSocial;
|
|
220
|
+
//# sourceMappingURL=footer-cta-social.cjs.map
|
|
221
|
+
//# sourceMappingURL=footer-cta-social.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/utils.ts","../components/ui/dynamic-icon.tsx","../components/blocks/footers/footer-cta-social.tsx"],"names":["twMerge","clsx","React","iconName","jsx","jsxs","React2"],"mappings":"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","file":"footer-cta-social.cjs","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\ninterface DynamicIconProps {\n /**\n * Icon name in format: prefix/name or prefix:name\n * Examples: \"lucide/home\", \"mdi:account\", \"heroicons/check\"\n */\n name: string;\n /**\n * Icon size in pixels\n * @default 28\n */\n size?: number;\n /**\n * Icon color - accepts any valid CSS color\n * Note: When not specified, the icon inherits color from parent via CSS currentColor\n */\n color?: string;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Alt text for accessibility\n */\n alt?: string;\n}\n\n// Simple in-memory cache for fetched SVGs\nconst svgCache = new Map<string, string>();\n\n/**\n * Lightweight icon component that dynamically loads SVG icons from icons.opensite.ai API.\n *\n * Features:\n * - Pulls SVGs from https://icons.opensite.ai API and inlines them for CSS color inheritance\n * - Supports currentColor - icons inherit color from parent element\n * - Accepts prefix/name or prefix:name format\n * - Customizable size and explicit color via props\n * - In-memory caching to prevent duplicate fetches\n *\n * @example\n * ```tsx\n * // Icon inherits color from parent (recommended for hover states, etc.)\n * <span className=\"text-white hover:text-red-500\">\n * <DynamicIcon name=\"lucide/home\" size={24} />\n * </span>\n *\n * // Icon with explicit color\n * <DynamicIcon name=\"mdi:account\" size={32} color=\"#ff0000\" />\n * ```\n */\nexport function DynamicIcon({\n name,\n size = 28,\n color,\n className,\n alt,\n}: DynamicIconProps) {\n const [svgContent, setSvgContent] = React.useState<string | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n\n const { url, iconName } = React.useMemo(() => {\n const separator = name.includes(\"/\") ? \"/\" : \":\";\n const [prefix, iconName] = name.split(separator);\n // Don't pass color to API - we'll handle it via CSS\n const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName}?format=svg&width=${size}&height=${size}`;\n\n return {\n url: baseUrl,\n iconName,\n };\n }, [name, size]);\n\n React.useEffect(() => {\n let isMounted = true;\n\n const fetchSvg = async () => {\n // Check cache first\n const cached = svgCache.get(url);\n if (cached) {\n if (isMounted) {\n setSvgContent(cached);\n setIsLoading(false);\n }\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch icon: ${response.status}`);\n }\n\n let svg = await response.text();\n\n // Process SVG to ensure currentColor works:\n // 1. Replace any hardcoded colors with currentColor\n // 2. Ensure stroke/fill use currentColor where appropriate\n svg = processSvgForCurrentColor(svg);\n\n // Cache the processed SVG\n svgCache.set(url, svg);\n\n if (isMounted) {\n setSvgContent(svg);\n setIsLoading(false);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err.message : \"Failed to load icon\");\n setIsLoading(false);\n }\n }\n };\n\n fetchSvg();\n\n return () => {\n isMounted = false;\n };\n }, [url]);\n\n // Loading state - show placeholder with same dimensions\n if (isLoading) {\n return (\n <span\n className={cn(\"inline-block\", className)}\n style={{ width: size, height: size }}\n aria-hidden=\"true\"\n />\n );\n }\n\n // Error state - show nothing or fallback\n if (error || !svgContent) {\n return (\n <span\n className={cn(\"inline-block\", className)}\n style={{ width: size, height: size }}\n role=\"img\"\n aria-label={alt || iconName}\n />\n );\n }\n\n // Render inline SVG\n // The color prop applies an explicit color, otherwise inherits from parent via currentColor\n return (\n <span\n className={cn(\"inline-flex items-center justify-center\", className)}\n style={{\n width: size,\n height: size,\n color: color || \"inherit\",\n }}\n role=\"img\"\n aria-label={alt || iconName}\n dangerouslySetInnerHTML={{ __html: svgContent }}\n />\n );\n}\n\n/**\n * Process SVG to ensure it uses currentColor for proper CSS inheritance.\n * This handles various icon libraries that may use different color approaches.\n */\nfunction processSvgForCurrentColor(svg: string): string {\n // Replace stroke=\"currentColor\" is already correct, but ensure fill also works\n // Some icons use fill=\"none\" with stroke, others use fill with no stroke\n\n // Ensure the SVG doesn't have hardcoded colors that should be currentColor\n // Common patterns to replace:\n // - stroke=\"#000\" or stroke=\"#000000\" or stroke=\"black\" -> stroke=\"currentColor\"\n // - fill=\"#000\" or fill=\"#000000\" or fill=\"black\" -> fill=\"currentColor\"\n\n let processed = svg;\n\n // Replace common black color values with currentColor for stroke\n processed = processed.replace(\n /stroke=[\"'](#000000|#000|black)[\"']/gi,\n 'stroke=\"currentColor\"'\n );\n\n // Replace common black color values with currentColor for fill\n // But be careful not to replace fill=\"none\"\n processed = processed.replace(\n /fill=[\"'](#000000|#000|black)[\"']/gi,\n 'fill=\"currentColor\"'\n );\n\n return processed;\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../../lib/utils\";\nimport { DynamicIcon } from \"../../ui/dynamic-icon\";\n\n/**\n * Social link configuration with icon\n */\nexport interface FooterCtaSocialLink {\n /** Icon name in format: prefix/name (e.g., \"lucide/twitter\") */\n icon: string;\n /** Link URL */\n url: string;\n /** Accessible label */\n label: string;\n}\n\n/**\n * Props for the FooterCtaSocial component\n */\nexport interface FooterCtaSocialProps {\n /** Additional CSS classes */\n className?: string;\n /** Pre-heading text */\n preHeading?: string;\n /** Main heading text */\n heading?: string;\n /** Description text */\n description?: string;\n /** CTA button text */\n buttonText?: string;\n /** CTA button URL */\n buttonUrl?: string;\n /** Contact email */\n email?: string;\n /** Social links */\n socialLinks?: FooterCtaSocialLink[];\n}\n\nconst defaultSocialLinks: FooterCtaSocialLink[] = [\n { icon: \"lucide/twitter\", url: \"#\", label: \"Twitter\" },\n { icon: \"lucide/instagram\", url: \"#\", label: \"Instagram\" },\n { icon: \"lucide/facebook\", url: \"#\", label: \"Facebook\" },\n];\n\n/**\n * FooterCtaSocial - A centered CTA footer with decorative lines and social icons.\n *\n * Features a centered layout with decorative gradient lines, pre-heading text,\n * large heading, description, prominent CTA button, social media icons, and\n * contact email. Ideal for landing pages, marketing sites, and businesses\n * that want a conversion-focused footer with strong visual appeal.\n */\nexport function FooterCtaSocial({\n className,\n preHeading = \"Let's connect\",\n heading = \"You want to scale faster? Try Opensite today.\",\n description = \"Join thousands of companies already using our platform to scale their operations\",\n buttonText = \"Get Started Now\",\n buttonUrl = \"#\",\n email = \"hello@opensite.ai\",\n socialLinks = defaultSocialLinks,\n}: FooterCtaSocialProps): React.JSX.Element {\n return (\n <section className={cn(\"relative py-32\", className)}>\n <div className=\"relative z-10 container\">\n <div className=\"mx-auto flex max-w-3xl flex-col items-center gap-2 text-center\">\n {/* Pre-heading with decorative lines */}\n <div className=\"flex w-full items-center gap-4\">\n <div className=\"h-px flex-1 bg-[linear-gradient(270deg,var(--primary,rgb(255,255,255))_0%,var(--secondary,rgb(0,0,0))_100%)] opacity-50\" />\n <p className=\"text-sm text-muted-foreground italic md:text-base\">\n {preHeading}\n </p>\n <div className=\"h-px flex-1 bg-[linear-gradient(270deg,var(--secondary,rgb(0,0,0))_0%,var(--primary,rgb(255,255,255))_100%)] opacity-50\" />\n </div>\n\n {/* Main heading */}\n <h2 className=\"py-6 text-5xl font-bold md:text-6xl\">{heading}</h2>\n\n {/* Description */}\n <p className=\"max-w-2xl text-base text-muted-foreground md:text-lg\">\n {description}\n </p>\n\n {/* CTA Button */}\n <a\n href={buttonUrl}\n className=\"group relative mt-4 inline-flex items-center gap-2 rounded-lg border bg-background px-8 py-4 text-base font-medium transition-all hover:bg-muted\"\n >\n <span>{buttonText}</span>\n <DynamicIcon\n name=\"lucide/arrow-up-right\"\n size={16}\n className=\"transition-transform group-hover:translate-x-1 group-hover:-translate-y-1\"\n />\n </a>\n\n {/* Social Media Links */}\n <div className=\"flex items-center gap-6 pt-8\">\n {socialLinks.map((link, idx) => (\n <React.Fragment key={idx}>\n <a\n href={link.url}\n className=\"text-muted-foreground transition-colors hover:text-foreground\"\n aria-label={link.label}\n >\n <DynamicIcon name={link.icon} size={20} />\n </a>\n {idx < socialLinks.length - 1 && (\n <div className=\"h-4 w-px bg-border\" />\n )}\n </React.Fragment>\n ))}\n </div>\n\n {/* Support Email */}\n <p className=\"pt-2 text-sm text-muted-foreground md:text-base\">\n <a\n href={`mailto:${email}`}\n className=\"transition-colors hover:text-foreground\"\n >\n {email}\n </a>\n </p>\n\n {/* Attribution */}\n <div className=\"mt-8 border-t pt-8 text-sm text-muted-foreground\">\n <p>© {new Date().getFullYear()} Opensite AI. All rights reserved.</p>\n <a\n href=\"https://opensite.ai\"\n className=\"mt-2 inline-block hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n AI Website and Automation Platform by Opensite\n </a>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Social link configuration with icon
|
|
5
|
+
*/
|
|
6
|
+
interface FooterCtaSocialLink {
|
|
7
|
+
/** Icon name in format: prefix/name (e.g., "lucide/twitter") */
|
|
8
|
+
icon: string;
|
|
9
|
+
/** Link URL */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Accessible label */
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Props for the FooterCtaSocial component
|
|
16
|
+
*/
|
|
17
|
+
interface FooterCtaSocialProps {
|
|
18
|
+
/** Additional CSS classes */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Pre-heading text */
|
|
21
|
+
preHeading?: string;
|
|
22
|
+
/** Main heading text */
|
|
23
|
+
heading?: string;
|
|
24
|
+
/** Description text */
|
|
25
|
+
description?: string;
|
|
26
|
+
/** CTA button text */
|
|
27
|
+
buttonText?: string;
|
|
28
|
+
/** CTA button URL */
|
|
29
|
+
buttonUrl?: string;
|
|
30
|
+
/** Contact email */
|
|
31
|
+
email?: string;
|
|
32
|
+
/** Social links */
|
|
33
|
+
socialLinks?: FooterCtaSocialLink[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* FooterCtaSocial - A centered CTA footer with decorative lines and social icons.
|
|
37
|
+
*
|
|
38
|
+
* Features a centered layout with decorative gradient lines, pre-heading text,
|
|
39
|
+
* large heading, description, prominent CTA button, social media icons, and
|
|
40
|
+
* contact email. Ideal for landing pages, marketing sites, and businesses
|
|
41
|
+
* that want a conversion-focused footer with strong visual appeal.
|
|
42
|
+
*/
|
|
43
|
+
declare function FooterCtaSocial({ className, preHeading, heading, description, buttonText, buttonUrl, email, socialLinks, }: FooterCtaSocialProps): React.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { FooterCtaSocial, type FooterCtaSocialLink, type FooterCtaSocialProps };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Social link configuration with icon
|
|
5
|
+
*/
|
|
6
|
+
interface FooterCtaSocialLink {
|
|
7
|
+
/** Icon name in format: prefix/name (e.g., "lucide/twitter") */
|
|
8
|
+
icon: string;
|
|
9
|
+
/** Link URL */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Accessible label */
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Props for the FooterCtaSocial component
|
|
16
|
+
*/
|
|
17
|
+
interface FooterCtaSocialProps {
|
|
18
|
+
/** Additional CSS classes */
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Pre-heading text */
|
|
21
|
+
preHeading?: string;
|
|
22
|
+
/** Main heading text */
|
|
23
|
+
heading?: string;
|
|
24
|
+
/** Description text */
|
|
25
|
+
description?: string;
|
|
26
|
+
/** CTA button text */
|
|
27
|
+
buttonText?: string;
|
|
28
|
+
/** CTA button URL */
|
|
29
|
+
buttonUrl?: string;
|
|
30
|
+
/** Contact email */
|
|
31
|
+
email?: string;
|
|
32
|
+
/** Social links */
|
|
33
|
+
socialLinks?: FooterCtaSocialLink[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* FooterCtaSocial - A centered CTA footer with decorative lines and social icons.
|
|
37
|
+
*
|
|
38
|
+
* Features a centered layout with decorative gradient lines, pre-heading text,
|
|
39
|
+
* large heading, description, prominent CTA button, social media icons, and
|
|
40
|
+
* contact email. Ideal for landing pages, marketing sites, and businesses
|
|
41
|
+
* that want a conversion-focused footer with strong visual appeal.
|
|
42
|
+
*/
|
|
43
|
+
declare function FooterCtaSocial({ className, preHeading, heading, description, buttonText, buttonUrl, email, socialLinks, }: FooterCtaSocialProps): React.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { FooterCtaSocial, type FooterCtaSocialLink, type FooterCtaSocialProps };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
import { twMerge } from 'tailwind-merge';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// components/blocks/footers/footer-cta-social.tsx
|
|
8
|
+
function cn(...inputs) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
11
|
+
var svgCache = /* @__PURE__ */ new Map();
|
|
12
|
+
function DynamicIcon({
|
|
13
|
+
name,
|
|
14
|
+
size = 28,
|
|
15
|
+
color,
|
|
16
|
+
className,
|
|
17
|
+
alt
|
|
18
|
+
}) {
|
|
19
|
+
const [svgContent, setSvgContent] = React.useState(null);
|
|
20
|
+
const [isLoading, setIsLoading] = React.useState(true);
|
|
21
|
+
const [error, setError] = React.useState(null);
|
|
22
|
+
const { url, iconName } = React.useMemo(() => {
|
|
23
|
+
const separator = name.includes("/") ? "/" : ":";
|
|
24
|
+
const [prefix, iconName2] = name.split(separator);
|
|
25
|
+
const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName2}?format=svg&width=${size}&height=${size}`;
|
|
26
|
+
return {
|
|
27
|
+
url: baseUrl,
|
|
28
|
+
iconName: iconName2
|
|
29
|
+
};
|
|
30
|
+
}, [name, size]);
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
let isMounted = true;
|
|
33
|
+
const fetchSvg = async () => {
|
|
34
|
+
const cached = svgCache.get(url);
|
|
35
|
+
if (cached) {
|
|
36
|
+
if (isMounted) {
|
|
37
|
+
setSvgContent(cached);
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
setIsLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
const response = await fetch(url);
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
throw new Error(`Failed to fetch icon: ${response.status}`);
|
|
48
|
+
}
|
|
49
|
+
let svg = await response.text();
|
|
50
|
+
svg = processSvgForCurrentColor(svg);
|
|
51
|
+
svgCache.set(url, svg);
|
|
52
|
+
if (isMounted) {
|
|
53
|
+
setSvgContent(svg);
|
|
54
|
+
setIsLoading(false);
|
|
55
|
+
}
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (isMounted) {
|
|
58
|
+
setError(err instanceof Error ? err.message : "Failed to load icon");
|
|
59
|
+
setIsLoading(false);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
fetchSvg();
|
|
64
|
+
return () => {
|
|
65
|
+
isMounted = false;
|
|
66
|
+
};
|
|
67
|
+
}, [url]);
|
|
68
|
+
if (isLoading) {
|
|
69
|
+
return /* @__PURE__ */ jsx(
|
|
70
|
+
"span",
|
|
71
|
+
{
|
|
72
|
+
className: cn("inline-block", className),
|
|
73
|
+
style: { width: size, height: size },
|
|
74
|
+
"aria-hidden": "true"
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (error || !svgContent) {
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"span",
|
|
81
|
+
{
|
|
82
|
+
className: cn("inline-block", className),
|
|
83
|
+
style: { width: size, height: size },
|
|
84
|
+
role: "img",
|
|
85
|
+
"aria-label": alt || iconName
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
return /* @__PURE__ */ jsx(
|
|
90
|
+
"span",
|
|
91
|
+
{
|
|
92
|
+
className: cn("inline-flex items-center justify-center", className),
|
|
93
|
+
style: {
|
|
94
|
+
width: size,
|
|
95
|
+
height: size,
|
|
96
|
+
color: color || "inherit"
|
|
97
|
+
},
|
|
98
|
+
role: "img",
|
|
99
|
+
"aria-label": alt || iconName,
|
|
100
|
+
dangerouslySetInnerHTML: { __html: svgContent }
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
function processSvgForCurrentColor(svg) {
|
|
105
|
+
let processed = svg;
|
|
106
|
+
processed = processed.replace(
|
|
107
|
+
/stroke=["'](#000000|#000|black)["']/gi,
|
|
108
|
+
'stroke="currentColor"'
|
|
109
|
+
);
|
|
110
|
+
processed = processed.replace(
|
|
111
|
+
/fill=["'](#000000|#000|black)["']/gi,
|
|
112
|
+
'fill="currentColor"'
|
|
113
|
+
);
|
|
114
|
+
return processed;
|
|
115
|
+
}
|
|
116
|
+
var defaultSocialLinks = [
|
|
117
|
+
{ icon: "lucide/twitter", url: "#", label: "Twitter" },
|
|
118
|
+
{ icon: "lucide/instagram", url: "#", label: "Instagram" },
|
|
119
|
+
{ icon: "lucide/facebook", url: "#", label: "Facebook" }
|
|
120
|
+
];
|
|
121
|
+
function FooterCtaSocial({
|
|
122
|
+
className,
|
|
123
|
+
preHeading = "Let's connect",
|
|
124
|
+
heading = "You want to scale faster? Try Opensite today.",
|
|
125
|
+
description = "Join thousands of companies already using our platform to scale their operations",
|
|
126
|
+
buttonText = "Get Started Now",
|
|
127
|
+
buttonUrl = "#",
|
|
128
|
+
email = "hello@opensite.ai",
|
|
129
|
+
socialLinks = defaultSocialLinks
|
|
130
|
+
}) {
|
|
131
|
+
return /* @__PURE__ */ jsx("section", { className: cn("relative py-32", className), children: /* @__PURE__ */ jsx("div", { className: "relative z-10 container", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex max-w-3xl flex-col items-center gap-2 text-center", children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-4", children: [
|
|
133
|
+
/* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-[linear-gradient(270deg,var(--primary,rgb(255,255,255))_0%,var(--secondary,rgb(0,0,0))_100%)] opacity-50" }),
|
|
134
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground italic md:text-base", children: preHeading }),
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: "h-px flex-1 bg-[linear-gradient(270deg,var(--secondary,rgb(0,0,0))_0%,var(--primary,rgb(255,255,255))_100%)] opacity-50" })
|
|
136
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsx("h2", { className: "py-6 text-5xl font-bold md:text-6xl", children: heading }),
|
|
138
|
+
/* @__PURE__ */ jsx("p", { className: "max-w-2xl text-base text-muted-foreground md:text-lg", children: description }),
|
|
139
|
+
/* @__PURE__ */ jsxs(
|
|
140
|
+
"a",
|
|
141
|
+
{
|
|
142
|
+
href: buttonUrl,
|
|
143
|
+
className: "group relative mt-4 inline-flex items-center gap-2 rounded-lg border bg-background px-8 py-4 text-base font-medium transition-all hover:bg-muted",
|
|
144
|
+
children: [
|
|
145
|
+
/* @__PURE__ */ jsx("span", { children: buttonText }),
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
DynamicIcon,
|
|
148
|
+
{
|
|
149
|
+
name: "lucide/arrow-up-right",
|
|
150
|
+
size: 16,
|
|
151
|
+
className: "transition-transform group-hover:translate-x-1 group-hover:-translate-y-1"
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
),
|
|
157
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-6 pt-8", children: socialLinks.map((link, idx) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
158
|
+
/* @__PURE__ */ jsx(
|
|
159
|
+
"a",
|
|
160
|
+
{
|
|
161
|
+
href: link.url,
|
|
162
|
+
className: "text-muted-foreground transition-colors hover:text-foreground",
|
|
163
|
+
"aria-label": link.label,
|
|
164
|
+
children: /* @__PURE__ */ jsx(DynamicIcon, { name: link.icon, size: 20 })
|
|
165
|
+
}
|
|
166
|
+
),
|
|
167
|
+
idx < socialLinks.length - 1 && /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" })
|
|
168
|
+
] }, idx)) }),
|
|
169
|
+
/* @__PURE__ */ jsx("p", { className: "pt-2 text-sm text-muted-foreground md:text-base", children: /* @__PURE__ */ jsx(
|
|
170
|
+
"a",
|
|
171
|
+
{
|
|
172
|
+
href: `mailto:${email}`,
|
|
173
|
+
className: "transition-colors hover:text-foreground",
|
|
174
|
+
children: email
|
|
175
|
+
}
|
|
176
|
+
) }),
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-8 border-t pt-8 text-sm text-muted-foreground", children: [
|
|
178
|
+
/* @__PURE__ */ jsxs("p", { children: [
|
|
179
|
+
"\xA9 ",
|
|
180
|
+
(/* @__PURE__ */ new Date()).getFullYear(),
|
|
181
|
+
" Opensite AI. All rights reserved."
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsx(
|
|
184
|
+
"a",
|
|
185
|
+
{
|
|
186
|
+
href: "https://opensite.ai",
|
|
187
|
+
className: "mt-2 inline-block hover:text-foreground",
|
|
188
|
+
target: "_blank",
|
|
189
|
+
rel: "noopener noreferrer",
|
|
190
|
+
children: "AI Website and Automation Platform by Opensite"
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] })
|
|
194
|
+
] }) }) });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { FooterCtaSocial };
|
|
198
|
+
//# sourceMappingURL=footer-cta-social.js.map
|
|
199
|
+
//# sourceMappingURL=footer-cta-social.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/utils.ts","../components/ui/dynamic-icon.tsx","../components/blocks/footers/footer-cta-social.tsx"],"names":["iconName","jsx","React2"],"mappings":"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","file":"footer-cta-social.js","sourcesContent":["import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n","\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"../../lib/utils\";\n\ninterface DynamicIconProps {\n /**\n * Icon name in format: prefix/name or prefix:name\n * Examples: \"lucide/home\", \"mdi:account\", \"heroicons/check\"\n */\n name: string;\n /**\n * Icon size in pixels\n * @default 28\n */\n size?: number;\n /**\n * Icon color - accepts any valid CSS color\n * Note: When not specified, the icon inherits color from parent via CSS currentColor\n */\n color?: string;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Alt text for accessibility\n */\n alt?: string;\n}\n\n// Simple in-memory cache for fetched SVGs\nconst svgCache = new Map<string, string>();\n\n/**\n * Lightweight icon component that dynamically loads SVG icons from icons.opensite.ai API.\n *\n * Features:\n * - Pulls SVGs from https://icons.opensite.ai API and inlines them for CSS color inheritance\n * - Supports currentColor - icons inherit color from parent element\n * - Accepts prefix/name or prefix:name format\n * - Customizable size and explicit color via props\n * - In-memory caching to prevent duplicate fetches\n *\n * @example\n * ```tsx\n * // Icon inherits color from parent (recommended for hover states, etc.)\n * <span className=\"text-white hover:text-red-500\">\n * <DynamicIcon name=\"lucide/home\" size={24} />\n * </span>\n *\n * // Icon with explicit color\n * <DynamicIcon name=\"mdi:account\" size={32} color=\"#ff0000\" />\n * ```\n */\nexport function DynamicIcon({\n name,\n size = 28,\n color,\n className,\n alt,\n}: DynamicIconProps) {\n const [svgContent, setSvgContent] = React.useState<string | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n const [error, setError] = React.useState<string | null>(null);\n\n const { url, iconName } = React.useMemo(() => {\n const separator = name.includes(\"/\") ? \"/\" : \":\";\n const [prefix, iconName] = name.split(separator);\n // Don't pass color to API - we'll handle it via CSS\n const baseUrl = `https://icons.opensite.ai/api/icon/${prefix}/${iconName}?format=svg&width=${size}&height=${size}`;\n\n return {\n url: baseUrl,\n iconName,\n };\n }, [name, size]);\n\n React.useEffect(() => {\n let isMounted = true;\n\n const fetchSvg = async () => {\n // Check cache first\n const cached = svgCache.get(url);\n if (cached) {\n if (isMounted) {\n setSvgContent(cached);\n setIsLoading(false);\n }\n return;\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch icon: ${response.status}`);\n }\n\n let svg = await response.text();\n\n // Process SVG to ensure currentColor works:\n // 1. Replace any hardcoded colors with currentColor\n // 2. Ensure stroke/fill use currentColor where appropriate\n svg = processSvgForCurrentColor(svg);\n\n // Cache the processed SVG\n svgCache.set(url, svg);\n\n if (isMounted) {\n setSvgContent(svg);\n setIsLoading(false);\n }\n } catch (err) {\n if (isMounted) {\n setError(err instanceof Error ? err.message : \"Failed to load icon\");\n setIsLoading(false);\n }\n }\n };\n\n fetchSvg();\n\n return () => {\n isMounted = false;\n };\n }, [url]);\n\n // Loading state - show placeholder with same dimensions\n if (isLoading) {\n return (\n <span\n className={cn(\"inline-block\", className)}\n style={{ width: size, height: size }}\n aria-hidden=\"true\"\n />\n );\n }\n\n // Error state - show nothing or fallback\n if (error || !svgContent) {\n return (\n <span\n className={cn(\"inline-block\", className)}\n style={{ width: size, height: size }}\n role=\"img\"\n aria-label={alt || iconName}\n />\n );\n }\n\n // Render inline SVG\n // The color prop applies an explicit color, otherwise inherits from parent via currentColor\n return (\n <span\n className={cn(\"inline-flex items-center justify-center\", className)}\n style={{\n width: size,\n height: size,\n color: color || \"inherit\",\n }}\n role=\"img\"\n aria-label={alt || iconName}\n dangerouslySetInnerHTML={{ __html: svgContent }}\n />\n );\n}\n\n/**\n * Process SVG to ensure it uses currentColor for proper CSS inheritance.\n * This handles various icon libraries that may use different color approaches.\n */\nfunction processSvgForCurrentColor(svg: string): string {\n // Replace stroke=\"currentColor\" is already correct, but ensure fill also works\n // Some icons use fill=\"none\" with stroke, others use fill with no stroke\n\n // Ensure the SVG doesn't have hardcoded colors that should be currentColor\n // Common patterns to replace:\n // - stroke=\"#000\" or stroke=\"#000000\" or stroke=\"black\" -> stroke=\"currentColor\"\n // - fill=\"#000\" or fill=\"#000000\" or fill=\"black\" -> fill=\"currentColor\"\n\n let processed = svg;\n\n // Replace common black color values with currentColor for stroke\n processed = processed.replace(\n /stroke=[\"'](#000000|#000|black)[\"']/gi,\n 'stroke=\"currentColor\"'\n );\n\n // Replace common black color values with currentColor for fill\n // But be careful not to replace fill=\"none\"\n processed = processed.replace(\n /fill=[\"'](#000000|#000|black)[\"']/gi,\n 'fill=\"currentColor\"'\n );\n\n return processed;\n}\n","\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../../../lib/utils\";\nimport { DynamicIcon } from \"../../ui/dynamic-icon\";\n\n/**\n * Social link configuration with icon\n */\nexport interface FooterCtaSocialLink {\n /** Icon name in format: prefix/name (e.g., \"lucide/twitter\") */\n icon: string;\n /** Link URL */\n url: string;\n /** Accessible label */\n label: string;\n}\n\n/**\n * Props for the FooterCtaSocial component\n */\nexport interface FooterCtaSocialProps {\n /** Additional CSS classes */\n className?: string;\n /** Pre-heading text */\n preHeading?: string;\n /** Main heading text */\n heading?: string;\n /** Description text */\n description?: string;\n /** CTA button text */\n buttonText?: string;\n /** CTA button URL */\n buttonUrl?: string;\n /** Contact email */\n email?: string;\n /** Social links */\n socialLinks?: FooterCtaSocialLink[];\n}\n\nconst defaultSocialLinks: FooterCtaSocialLink[] = [\n { icon: \"lucide/twitter\", url: \"#\", label: \"Twitter\" },\n { icon: \"lucide/instagram\", url: \"#\", label: \"Instagram\" },\n { icon: \"lucide/facebook\", url: \"#\", label: \"Facebook\" },\n];\n\n/**\n * FooterCtaSocial - A centered CTA footer with decorative lines and social icons.\n *\n * Features a centered layout with decorative gradient lines, pre-heading text,\n * large heading, description, prominent CTA button, social media icons, and\n * contact email. Ideal for landing pages, marketing sites, and businesses\n * that want a conversion-focused footer with strong visual appeal.\n */\nexport function FooterCtaSocial({\n className,\n preHeading = \"Let's connect\",\n heading = \"You want to scale faster? Try Opensite today.\",\n description = \"Join thousands of companies already using our platform to scale their operations\",\n buttonText = \"Get Started Now\",\n buttonUrl = \"#\",\n email = \"hello@opensite.ai\",\n socialLinks = defaultSocialLinks,\n}: FooterCtaSocialProps): React.JSX.Element {\n return (\n <section className={cn(\"relative py-32\", className)}>\n <div className=\"relative z-10 container\">\n <div className=\"mx-auto flex max-w-3xl flex-col items-center gap-2 text-center\">\n {/* Pre-heading with decorative lines */}\n <div className=\"flex w-full items-center gap-4\">\n <div className=\"h-px flex-1 bg-[linear-gradient(270deg,var(--primary,rgb(255,255,255))_0%,var(--secondary,rgb(0,0,0))_100%)] opacity-50\" />\n <p className=\"text-sm text-muted-foreground italic md:text-base\">\n {preHeading}\n </p>\n <div className=\"h-px flex-1 bg-[linear-gradient(270deg,var(--secondary,rgb(0,0,0))_0%,var(--primary,rgb(255,255,255))_100%)] opacity-50\" />\n </div>\n\n {/* Main heading */}\n <h2 className=\"py-6 text-5xl font-bold md:text-6xl\">{heading}</h2>\n\n {/* Description */}\n <p className=\"max-w-2xl text-base text-muted-foreground md:text-lg\">\n {description}\n </p>\n\n {/* CTA Button */}\n <a\n href={buttonUrl}\n className=\"group relative mt-4 inline-flex items-center gap-2 rounded-lg border bg-background px-8 py-4 text-base font-medium transition-all hover:bg-muted\"\n >\n <span>{buttonText}</span>\n <DynamicIcon\n name=\"lucide/arrow-up-right\"\n size={16}\n className=\"transition-transform group-hover:translate-x-1 group-hover:-translate-y-1\"\n />\n </a>\n\n {/* Social Media Links */}\n <div className=\"flex items-center gap-6 pt-8\">\n {socialLinks.map((link, idx) => (\n <React.Fragment key={idx}>\n <a\n href={link.url}\n className=\"text-muted-foreground transition-colors hover:text-foreground\"\n aria-label={link.label}\n >\n <DynamicIcon name={link.icon} size={20} />\n </a>\n {idx < socialLinks.length - 1 && (\n <div className=\"h-4 w-px bg-border\" />\n )}\n </React.Fragment>\n ))}\n </div>\n\n {/* Support Email */}\n <p className=\"pt-2 text-sm text-muted-foreground md:text-base\">\n <a\n href={`mailto:${email}`}\n className=\"transition-colors hover:text-foreground\"\n >\n {email}\n </a>\n </p>\n\n {/* Attribution */}\n <div className=\"mt-8 border-t pt-8 text-sm text-muted-foreground\">\n <p>© {new Date().getFullYear()} Opensite AI. All rights reserved.</p>\n <a\n href=\"https://opensite.ai\"\n className=\"mt-2 inline-block hover:text-foreground\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n AI Website and Automation Platform by Opensite\n </a>\n </div>\n </div>\n </div>\n </section>\n );\n}\n"]}
|