@opensite/ui 1.8.6 → 1.8.8

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.
@@ -1,195 +1,392 @@
1
1
  "use client";
2
- import * as React from 'react';
3
- import React__default, { useMemo } from 'react';
2
+ import * as React3 from 'react';
3
+ import React3__default, { useMemo } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
- import { cva } from 'class-variance-authority';
7
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
6
  import { Img } from '@page-speed/img';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { cva } from 'class-variance-authority';
9
9
  import { Slot } from '@radix-ui/react-slot';
10
10
 
11
11
  // components/blocks/hero/hero-stats-social-proof.tsx
12
12
  function cn(...inputs) {
13
13
  return twMerge(clsx(inputs));
14
14
  }
15
- function normalizePhoneNumber(input) {
16
- const trimmed = input.trim();
17
- if (trimmed.toLowerCase().startsWith("tel:")) {
18
- return trimmed;
19
- }
20
- const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
21
- if (match) {
22
- const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
23
- const extension = match[3];
24
- const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
25
- const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
26
- return `tel:${withExtension}`;
27
- }
28
- const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
29
- return `tel:${cleaned}`;
30
- }
31
- function normalizeEmail(input) {
32
- const trimmed = input.trim();
33
- if (trimmed.toLowerCase().startsWith("mailto:")) {
34
- return trimmed;
35
- }
36
- return `mailto:${trimmed}`;
37
- }
38
- function isEmail(input) {
39
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
40
- return emailRegex.test(input.trim());
41
- }
42
- function isPhoneNumber(input) {
43
- const trimmed = input.trim();
44
- if (trimmed.toLowerCase().startsWith("tel:")) {
45
- return true;
46
- }
47
- const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
48
- return phoneRegex.test(trimmed);
49
- }
50
- function isInternalUrl(href) {
51
- if (typeof window === "undefined") {
52
- return href.startsWith("/") && !href.startsWith("//");
53
- }
54
- const trimmed = href.trim();
55
- if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
56
- return true;
57
- }
58
- try {
59
- const url = new URL(trimmed, window.location.href);
60
- const currentOrigin = window.location.origin;
61
- const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
62
- return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
63
- } catch {
64
- return false;
15
+ var maxWidthStyles = {
16
+ sm: "max-w-screen-sm",
17
+ md: "max-w-screen-md",
18
+ lg: "max-w-screen-lg",
19
+ xl: "max-w-7xl",
20
+ "2xl": "max-w-screen-2xl",
21
+ "4xl": "max-w-[1536px]",
22
+ full: "max-w-full"
23
+ };
24
+ var Container = React3__default.forwardRef(
25
+ ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
26
+ const Component = as;
27
+ return /* @__PURE__ */ jsx(
28
+ Component,
29
+ {
30
+ ref,
31
+ className: cn(
32
+ "mx-auto w-full px-2 sm:px-4 lg:px-8",
33
+ maxWidthStyles[maxWidth],
34
+ className
35
+ ),
36
+ ...props,
37
+ children
38
+ }
39
+ );
65
40
  }
66
- }
67
- function toRelativePath(href) {
68
- if (typeof window === "undefined") {
69
- return href;
41
+ );
42
+ Container.displayName = "Container";
43
+
44
+ // lib/patternSvgs.ts
45
+ var patternSvgs = {
46
+ squareAltGrid: "https://cdn.ing/assets/files/record/286187/4gpn0yq2ptra8iwlvmwwv860ggwv",
47
+ grid1: "https://cdn.ing/assets/files/record/286186/nbdflpgp4ostrno079hygibsflp3",
48
+ noise: "https://cdn.ing/assets/i/r/286188/zrqcp9hynh3j7p2laihwzfbujgrl/noise.png",
49
+ dots: "https://cdn.ing/assets/files/record/286198/yfsjx9thvtxzhl2qtshxyhkrm524",
50
+ dotPattern: "https://cdn.ing/assets/files/record/286192/7ig0cku8aqbboiza8nuk6hw0nnsr",
51
+ dotPattern2: "https://cdn.ing/assets/files/record/286189/arez6gd2s7isn9i1o6c7sexdq7bl",
52
+ circles: "https://cdn.ing/assets/files/record/286190/gtmia3sncjtzetdshc20zf1d3c17",
53
+ waves: "https://cdn.ing/assets/files/record/286191/mqlb33fzxz9cdth1bx7if0wmpkp1",
54
+ crossPattern: "https://cdn.ing/assets/files/record/286193/9yfqwdbnqaipbp7fsb3wbzzmq472",
55
+ architect: "https://cdn.ing/assets/files/record/286194/vgs88ugpvyhxu13wqgy0acvae6re",
56
+ tinyCheckers: "https://cdn.ing/assets/files/record/286195/65efaknsw8kcpf9o3c2gybytsl5b",
57
+ p6: "https://cdn.ing/assets/i/r/286196/6kl0rqnd6mjk8j7e525fo8fo0vkc/p6.webp"
58
+ };
59
+ var maskTop = "radial-gradient(ellipse 70% 60% at 50% 0%, #000 60%, transparent 100%)";
60
+ var maskBottom = "radial-gradient(ellipse 100% 80% at 50% 100%, #000 50%, transparent 90%)";
61
+ var maskCenter = "radial-gradient(ellipse 60% 60% at 50% 50%, #000 30%, transparent 70%)";
62
+ var maskTopLeft = "radial-gradient(ellipse 80% 80% at 0% 0%, #000 50%, transparent 90%)";
63
+ var maskTopRight = "radial-gradient(ellipse 80% 80% at 100% 0%, #000 50%, transparent 90%)";
64
+ var maskBottomLeft = "radial-gradient(ellipse 80% 80% at 0% 100%, #000 50%, transparent 90%)";
65
+ var maskBottomRight = "radial-gradient(ellipse 80% 80% at 100% 100%, #000 50%, transparent 90%)";
66
+ var circuitBoardPattern = (id, mask) => /* @__PURE__ */ jsxs(
67
+ "svg",
68
+ {
69
+ className: "h-full w-full",
70
+ xmlns: "http://www.w3.org/2000/svg",
71
+ style: mask ? {
72
+ maskImage: mask,
73
+ WebkitMaskImage: mask
74
+ } : void 0,
75
+ children: [
76
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
77
+ "pattern",
78
+ {
79
+ id,
80
+ x: "0",
81
+ y: "0",
82
+ width: "100",
83
+ height: "100",
84
+ patternUnits: "userSpaceOnUse",
85
+ children: [
86
+ /* @__PURE__ */ jsx(
87
+ "path",
88
+ {
89
+ d: "M0 50h40M60 50h40M50 0v40M50 60v40",
90
+ stroke: "hsl(var(--muted))",
91
+ strokeWidth: "1",
92
+ fill: "none"
93
+ }
94
+ ),
95
+ /* @__PURE__ */ jsx("circle", { cx: "50", cy: "50", r: "3", fill: "hsl(var(--muted))" }),
96
+ /* @__PURE__ */ jsx("circle", { cx: "0", cy: "50", r: "2", fill: "hsl(var(--muted))" }),
97
+ /* @__PURE__ */ jsx("circle", { cx: "100", cy: "50", r: "2", fill: "hsl(var(--muted))" }),
98
+ /* @__PURE__ */ jsx("circle", { cx: "50", cy: "0", r: "2", fill: "hsl(var(--muted))" }),
99
+ /* @__PURE__ */ jsx("circle", { cx: "50", cy: "100", r: "2", fill: "hsl(var(--muted))" })
100
+ ]
101
+ }
102
+ ) }),
103
+ /* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: `url(#${id})` })
104
+ ]
70
105
  }
71
- const trimmed = href.trim();
72
- if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
73
- return trimmed;
106
+ );
107
+ var gridDotsPattern = (id, mask) => /* @__PURE__ */ jsxs(
108
+ "svg",
109
+ {
110
+ className: "h-full w-full",
111
+ xmlns: "http://www.w3.org/2000/svg",
112
+ style: mask ? {
113
+ maskImage: mask,
114
+ WebkitMaskImage: mask
115
+ } : void 0,
116
+ children: [
117
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
118
+ "pattern",
119
+ {
120
+ id,
121
+ x: "0",
122
+ y: "0",
123
+ width: "40",
124
+ height: "40",
125
+ patternUnits: "userSpaceOnUse",
126
+ children: [
127
+ /* @__PURE__ */ jsx(
128
+ "path",
129
+ {
130
+ d: "M0 20h40M20 0v40",
131
+ stroke: "hsl(var(--muted))",
132
+ strokeWidth: "0.5",
133
+ fill: "none"
134
+ }
135
+ ),
136
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "20", r: "2", fill: "hsl(var(--muted))" })
137
+ ]
138
+ }
139
+ ) }),
140
+ /* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: `url(#${id})` })
141
+ ]
74
142
  }
75
- try {
76
- const url = new URL(trimmed, window.location.href);
77
- const currentOrigin = window.location.origin;
78
- const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
79
- if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
80
- return url.pathname + url.search + url.hash;
143
+ );
144
+ var gridPattern = (size, mask) => /* @__PURE__ */ jsx(
145
+ "div",
146
+ {
147
+ className: "h-full w-full bg-[linear-gradient(to_right,_hsl(var(--muted))_1px,_transparent_1px),linear-gradient(to_bottom,_hsl(var(--muted))_1px,_transparent_1px)]",
148
+ style: {
149
+ backgroundSize: `${size}px ${size}px`,
150
+ ...mask ? {
151
+ maskImage: mask,
152
+ WebkitMaskImage: mask
153
+ } : {}
81
154
  }
82
- } catch {
83
155
  }
84
- return trimmed;
85
- }
86
- function useNavigation({
87
- href,
88
- onClick
89
- } = {}) {
90
- const linkType = React.useMemo(() => {
91
- if (!href || href.trim() === "") {
92
- return onClick ? "none" : "none";
156
+ );
157
+ var diagonalCrossPattern = (mask) => /* @__PURE__ */ jsx(
158
+ "div",
159
+ {
160
+ className: "h-full w-full",
161
+ style: {
162
+ backgroundImage: "repeating-linear-gradient(45deg, transparent, transparent 32px, hsl(var(--muted)) 32px, hsl(var(--muted)) 33px), repeating-linear-gradient(135deg, transparent, transparent 32px, hsl(var(--muted)) 32px, hsl(var(--muted)) 33px)",
163
+ ...mask ? {
164
+ maskImage: mask,
165
+ WebkitMaskImage: mask
166
+ } : {}
93
167
  }
94
- const trimmed = href.trim();
95
- if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
96
- return "mailto";
168
+ }
169
+ );
170
+ var dashedGridMaskBase = "repeating-linear-gradient(to right, black 0px, black 3px, transparent 3px, transparent 8px), repeating-linear-gradient(to bottom, black 0px, black 3px, transparent 3px, transparent 8px)";
171
+ var dashedGridPattern = (fadeMask) => {
172
+ const mask = fadeMask ? `${dashedGridMaskBase}, ${fadeMask}` : dashedGridMaskBase;
173
+ return /* @__PURE__ */ jsx(
174
+ "div",
175
+ {
176
+ className: "h-full w-full",
177
+ style: {
178
+ backgroundImage: "linear-gradient(to right, hsl(var(--muted)) 1px, transparent 1px), linear-gradient(to bottom, hsl(var(--muted)) 1px, transparent 1px)",
179
+ backgroundSize: "20px 20px",
180
+ backgroundPosition: "0 0, 0 0",
181
+ maskImage: mask,
182
+ WebkitMaskImage: mask,
183
+ maskComposite: "intersect",
184
+ WebkitMaskComposite: "source-in"
185
+ }
97
186
  }
98
- if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
99
- return "tel";
187
+ );
188
+ };
189
+ var gradientGlow = (position) => /* @__PURE__ */ jsx(
190
+ "div",
191
+ {
192
+ className: cn(
193
+ "pointer-events-none absolute left-1/2 z-0 aspect-square w-3/4 -translate-x-1/2 rounded-full opacity-50 blur-3xl",
194
+ position === "top" ? "-top-1/4" : "-bottom-1/4"
195
+ ),
196
+ style: {
197
+ background: "radial-gradient(circle, hsl(var(--primary)) 0%, transparent 70%)"
100
198
  }
101
- if (isInternalUrl(trimmed)) {
102
- return "internal";
103
- }
104
- try {
105
- new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
106
- return "external";
107
- } catch {
108
- return "internal";
109
- }
110
- }, [href, onClick]);
111
- const normalizedHref = React.useMemo(() => {
112
- if (!href || href.trim() === "") {
113
- return void 0;
114
- }
115
- const trimmed = href.trim();
116
- switch (linkType) {
117
- case "tel":
118
- return normalizePhoneNumber(trimmed);
119
- case "mailto":
120
- return normalizeEmail(trimmed);
121
- case "internal":
122
- return toRelativePath(trimmed);
123
- case "external":
124
- return trimmed;
125
- default:
126
- return trimmed;
127
- }
128
- }, [href, linkType]);
129
- const target = React.useMemo(() => {
130
- switch (linkType) {
131
- case "external":
132
- return "_blank";
133
- case "internal":
134
- return "_self";
135
- case "mailto":
136
- case "tel":
137
- return void 0;
138
- default:
139
- return void 0;
140
- }
141
- }, [linkType]);
142
- const rel = React.useMemo(() => {
143
- if (linkType === "external") {
144
- return "noopener noreferrer";
199
+ }
200
+ );
201
+ var spotlight = (position) => /* @__PURE__ */ jsx(
202
+ "div",
203
+ {
204
+ className: cn(
205
+ "pointer-events-none absolute top-1/2 z-0 aspect-square w-3/4 -translate-y-1/2 rounded-full opacity-40 blur-3xl",
206
+ position === "left" ? "-left-1/4" : "-right-1/4"
207
+ ),
208
+ style: {
209
+ background: "radial-gradient(circle, hsl(var(--primary)) 0%, transparent 70%)"
145
210
  }
146
- return void 0;
147
- }, [linkType]);
148
- const isExternal = linkType === "external";
149
- const isInternal = linkType === "internal";
150
- const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
151
- const handleClick = React.useCallback(
152
- (event) => {
153
- if (onClick) {
154
- try {
155
- onClick(event);
156
- } catch (error) {
157
- console.error("Error in user onClick handler:", error);
158
- }
159
- }
160
- if (event.defaultPrevented) {
161
- return;
211
+ }
212
+ );
213
+ var patternOverlays = {
214
+ circuitBoardBasic: () => circuitBoardPattern("circuit-board-basic"),
215
+ circuitBoardFadeTop: () => circuitBoardPattern("circuit-board-fade-top", maskTop),
216
+ circuitBoardFadeBottom: () => circuitBoardPattern("circuit-board-fade-bottom", maskBottom),
217
+ circuitBoardFadeCenter: () => circuitBoardPattern("circuit-board-fade-center", maskCenter),
218
+ circuitBoardFadeTopLeft: () => circuitBoardPattern("circuit-board-fade-top-left", maskTopLeft),
219
+ circuitBoardFadeTopRight: () => circuitBoardPattern("circuit-board-fade-top-right", maskTopRight),
220
+ circuitBoardFadeBottomLeft: () => circuitBoardPattern("circuit-board-fade-bottom-left", maskBottomLeft),
221
+ circuitBoardFadeBottomRight: () => circuitBoardPattern("circuit-board-fade-bottom-right", maskBottomRight),
222
+ dashedGridBasic: () => dashedGridPattern(),
223
+ dashedGridFadeTop: () => dashedGridPattern(maskTop),
224
+ dashedGridFadeBottom: () => dashedGridPattern(maskBottom),
225
+ dashedGridFadeCenter: () => dashedGridPattern(maskCenter),
226
+ dashedGridFadeTopLeft: () => dashedGridPattern(maskTopLeft),
227
+ dashedGridFadeTopRight: () => dashedGridPattern(maskTopRight),
228
+ dashedGridFadeBottomLeft: () => dashedGridPattern(maskBottomLeft),
229
+ dashedGridFadeBottomRight: () => dashedGridPattern(maskBottomRight),
230
+ diagonalCrossBasic: () => diagonalCrossPattern(),
231
+ diagonalCrossFadeTop: () => diagonalCrossPattern(maskTop),
232
+ diagonalCrossFadeBottom: () => diagonalCrossPattern(maskBottom),
233
+ diagonalCrossFadeCenter: () => diagonalCrossPattern(maskCenter),
234
+ diagonalCrossFadeTopLeft: () => diagonalCrossPattern(maskTopLeft),
235
+ diagonalCrossFadeTopRight: () => diagonalCrossPattern(maskTopRight),
236
+ diagonalCrossFadeBottomLeft: () => diagonalCrossPattern(maskBottomLeft),
237
+ diagonalCrossFadeBottomRight: () => diagonalCrossPattern(maskBottomRight),
238
+ gridBasic: () => gridPattern(40),
239
+ gridFadeTop: () => gridPattern(32, maskTop),
240
+ gridFadeBottom: () => gridPattern(32, maskBottom),
241
+ gridFadeCenter: () => gridPattern(40, maskCenter),
242
+ gridFadeTopLeft: () => gridPattern(32, maskTopLeft),
243
+ gridFadeTopRight: () => gridPattern(32, maskTopRight),
244
+ gridFadeBottomLeft: () => gridPattern(32, maskBottomLeft),
245
+ gridFadeBottomRight: () => gridPattern(32, maskBottomRight),
246
+ gridDotsBasic: () => gridDotsPattern("grid-dots-basic"),
247
+ gridDotsFadeCenter: () => gridDotsPattern("grid-dots-fade-center", maskCenter),
248
+ gradientGlowTop: () => gradientGlow("top"),
249
+ gradientGlowBottom: () => gradientGlow("bottom"),
250
+ spotlightLeft: () => spotlight("left"),
251
+ spotlightRight: () => spotlight("right")
252
+ };
253
+ var inlinePatternStyles = {
254
+ radialGradientTop: {
255
+ background: "radial-gradient(125% 125% at 50% 10%, hsl(var(--background)) 40%, hsl(var(--primary)) 100%)"
256
+ },
257
+ radialGradientBottom: {
258
+ background: "radial-gradient(125% 125% at 50% 90%, hsl(var(--background)) 40%, hsl(var(--primary)) 100%)"
259
+ }
260
+ };
261
+ function PatternBackground({
262
+ pattern,
263
+ opacity = 0.08,
264
+ className,
265
+ style
266
+ }) {
267
+ if (!pattern) {
268
+ return null;
269
+ }
270
+ if (pattern in inlinePatternStyles) {
271
+ const inlineStyle = inlinePatternStyles[pattern];
272
+ return /* @__PURE__ */ jsx(
273
+ "div",
274
+ {
275
+ className: cn("pointer-events-none absolute inset-0 z-0", className),
276
+ style: { ...inlineStyle, opacity, ...style },
277
+ "aria-hidden": "true"
162
278
  }
163
- if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
164
- !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
165
- if (typeof window !== "undefined") {
166
- const handler = window.__opensiteNavigationHandler;
167
- if (typeof handler === "function") {
168
- try {
169
- const handled = handler(normalizedHref, event.nativeEvent || event);
170
- if (handled !== false) {
171
- event.preventDefault();
172
- }
173
- } catch (error) {
174
- console.error("Error in navigation handler:", error);
175
- }
176
- }
177
- }
279
+ );
280
+ }
281
+ if (pattern in patternOverlays) {
282
+ const Overlay = patternOverlays[pattern];
283
+ return /* @__PURE__ */ jsx(
284
+ "div",
285
+ {
286
+ className: cn("pointer-events-none absolute inset-0 z-0", className),
287
+ style: { opacity, ...style },
288
+ "aria-hidden": "true",
289
+ children: Overlay()
178
290
  }
179
- },
180
- [onClick, shouldUseRouter, normalizedHref]
291
+ );
292
+ }
293
+ const patternUrl = pattern in patternSvgs ? patternSvgs[pattern] : pattern;
294
+ return /* @__PURE__ */ jsx(
295
+ "div",
296
+ {
297
+ className: cn("pointer-events-none absolute inset-0 z-0", className),
298
+ style: {
299
+ backgroundImage: `url(${patternUrl})`,
300
+ backgroundRepeat: "repeat",
301
+ backgroundSize: "auto",
302
+ opacity,
303
+ ...style
304
+ },
305
+ "aria-hidden": "true"
306
+ }
181
307
  );
182
- return {
183
- linkType,
184
- normalizedHref,
185
- target,
186
- rel,
187
- isExternal,
188
- isInternal,
189
- shouldUseRouter,
190
- handleClick
191
- };
192
308
  }
309
+ var backgroundStyles = {
310
+ default: "bg-background text-foreground",
311
+ white: "bg-white text-dark",
312
+ gray: "bg-muted/30 text-foreground",
313
+ dark: "bg-foreground text-background",
314
+ transparent: "bg-transparent text-foreground",
315
+ gradient: "bg-linear-to-br from-primary via-primary/90 to-foreground text-primary-foreground",
316
+ primary: "bg-primary text-primary-foreground",
317
+ secondary: "bg-secondary text-secondary-foreground",
318
+ muted: "bg-muted text-muted-foreground"
319
+ };
320
+ var spacingStyles = {
321
+ none: "py-0 md:py-0",
322
+ sm: "py-12 md:py-16",
323
+ md: "py-16 md:py-24",
324
+ lg: "py-20 md:py-32",
325
+ xl: "py-24 md:py-40"
326
+ };
327
+ var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
328
+ var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
329
+ var Section = React3__default.forwardRef(
330
+ ({
331
+ id,
332
+ title,
333
+ subtitle,
334
+ children,
335
+ className,
336
+ style,
337
+ background = "default",
338
+ spacing = "lg",
339
+ pattern,
340
+ patternOpacity,
341
+ patternClassName,
342
+ containerClassName,
343
+ containerMaxWidth = "xl",
344
+ ...props
345
+ }, ref) => {
346
+ const effectivePatternOpacity = patternOpacity !== void 0 ? patternOpacity : pattern ? 1 : 0;
347
+ return /* @__PURE__ */ jsxs(
348
+ "section",
349
+ {
350
+ ref,
351
+ id,
352
+ className: cn(
353
+ "relative",
354
+ pattern ? "overflow-hidden" : null,
355
+ backgroundStyles[background],
356
+ isPredefinedSpacing(spacing) ? spacingStyles[spacing] : spacing,
357
+ className
358
+ ),
359
+ style,
360
+ ...props,
361
+ children: [
362
+ /* @__PURE__ */ jsx(
363
+ PatternBackground,
364
+ {
365
+ pattern,
366
+ opacity: effectivePatternOpacity,
367
+ className: patternClassName
368
+ }
369
+ ),
370
+ /* @__PURE__ */ jsxs(
371
+ Container,
372
+ {
373
+ maxWidth: containerMaxWidth,
374
+ className: cn("relative z-10", containerClassName),
375
+ children: [
376
+ (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "mb-6 text-center md:mb-16", children: [
377
+ subtitle && /* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-semibold uppercase tracking-wider", children: subtitle }),
378
+ title && /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl", children: title })
379
+ ] }),
380
+ children
381
+ ]
382
+ }
383
+ )
384
+ ]
385
+ }
386
+ );
387
+ }
388
+ );
389
+ Section.displayName = "Section";
193
390
  var baseStyles = [
194
391
  // Layout
195
392
  "inline-flex items-center justify-center gap-2 whitespace-nowrap shrink-0",
@@ -285,557 +482,428 @@ var buttonVariants = cva(baseStyles, {
285
482
  ].join(" "),
286
483
  // Link variant - full customization
287
484
  link: [
288
- "bg-[var(--button-link-bg,transparent)]",
289
- "text-[var(--button-link-fg,hsl(var(--primary)))]",
290
- "border-[length:var(--button-link-border-width,0px)]",
291
- "border-[color:var(--button-link-border,transparent)]",
292
- "[box-shadow:var(--button-link-shadow,none)]",
293
- "hover:bg-[var(--button-link-hover-bg,transparent)]",
294
- "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
295
- "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
296
- "underline-offset-4 hover:underline"
297
- ].join(" ")
298
- },
299
- size: {
300
- default: [
301
- "h-[var(--button-height-md,2.25rem)]",
302
- "px-[var(--button-padding-x-md,1rem)]",
303
- "py-[var(--button-padding-y-md,0.5rem)]",
304
- "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
305
- ].join(" "),
306
- sm: [
307
- "h-[var(--button-height-sm,2rem)]",
308
- "px-[var(--button-padding-x-sm,0.75rem)]",
309
- "py-[var(--button-padding-y-sm,0.25rem)]",
310
- "gap-1.5",
311
- "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
312
- ].join(" "),
313
- md: [
314
- "h-[var(--button-height-md,2.25rem)]",
315
- "px-[var(--button-padding-x-md,1rem)]",
316
- "py-[var(--button-padding-y-md,0.5rem)]",
317
- "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
318
- ].join(" "),
319
- lg: [
320
- "h-[var(--button-height-lg,2.5rem)]",
321
- "px-[var(--button-padding-x-lg,1.5rem)]",
322
- "py-[var(--button-padding-y-lg,0.5rem)]",
323
- "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
324
- ].join(" "),
325
- icon: "size-[var(--button-height-md,2.25rem)]",
326
- "icon-sm": "size-[var(--button-height-sm,2rem)]",
327
- "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
328
- }
329
- },
330
- defaultVariants: {
331
- variant: "default",
332
- size: "default"
333
- }
334
- });
335
- var Pressable = React.forwardRef(
336
- ({
337
- children,
338
- className,
339
- href,
340
- onClick,
341
- variant,
342
- size,
343
- asButton = false,
344
- fallbackComponentType = "span",
345
- componentType,
346
- "aria-label": ariaLabel,
347
- "aria-describedby": ariaDescribedby,
348
- id,
349
- ...props
350
- }, ref) => {
351
- const navigation = useNavigation({ href, onClick });
352
- const {
353
- normalizedHref,
354
- target,
355
- rel,
356
- linkType,
357
- isInternal,
358
- handleClick
359
- } = navigation;
360
- const shouldRenderLink = normalizedHref && linkType !== "none";
361
- const shouldRenderButton = !shouldRenderLink && onClick;
362
- const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
363
- const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
364
- const shouldApplyButtonStyles = asButton || variant || size;
365
- const combinedClassName = cn(
366
- shouldApplyButtonStyles && buttonVariants({ variant, size }),
367
- className
368
- );
369
- const dataProps = Object.fromEntries(
370
- Object.entries(props).filter(([key]) => key.startsWith("data-"))
371
- );
372
- const buttonDataAttributes = shouldApplyButtonStyles ? {
373
- "data-slot": "button",
374
- "data-variant": variant ?? "default",
375
- "data-size": size ?? "default"
376
- } : {};
377
- const commonProps = {
378
- className: combinedClassName,
379
- onClick: handleClick,
380
- "aria-label": ariaLabel,
381
- "aria-describedby": ariaDescribedby,
382
- id,
383
- ...dataProps,
384
- ...buttonDataAttributes
385
- };
386
- if (finalComponentType === "a" && shouldRenderLink) {
387
- return /* @__PURE__ */ jsx(
388
- "a",
389
- {
390
- ref,
391
- href: normalizedHref,
392
- target,
393
- rel,
394
- ...commonProps,
395
- ...props,
396
- children
397
- }
398
- );
399
- }
400
- if (finalComponentType === "button") {
401
- return /* @__PURE__ */ jsx(
402
- "button",
403
- {
404
- ref,
405
- type: props.type || "button",
406
- ...commonProps,
407
- ...props,
408
- children
409
- }
410
- );
411
- }
412
- if (finalComponentType === "div") {
413
- return /* @__PURE__ */ jsx(
414
- "div",
415
- {
416
- ref,
417
- ...commonProps,
418
- children
419
- }
420
- );
485
+ "bg-[var(--button-link-bg,transparent)]",
486
+ "text-[var(--button-link-fg,hsl(var(--primary)))]",
487
+ "border-[length:var(--button-link-border-width,0px)]",
488
+ "border-[color:var(--button-link-border,transparent)]",
489
+ "[box-shadow:var(--button-link-shadow,none)]",
490
+ "hover:bg-[var(--button-link-hover-bg,transparent)]",
491
+ "hover:text-[var(--button-link-hover-fg,var(--button-link-fg,hsl(var(--primary))))]",
492
+ "hover:[box-shadow:var(--button-link-shadow-hover,none)]",
493
+ "underline-offset-4 hover:underline"
494
+ ].join(" ")
495
+ },
496
+ size: {
497
+ default: [
498
+ "h-[var(--button-height-md,2.25rem)]",
499
+ "px-[var(--button-padding-x-md,1rem)]",
500
+ "py-[var(--button-padding-y-md,0.5rem)]",
501
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
502
+ ].join(" "),
503
+ sm: [
504
+ "h-[var(--button-height-sm,2rem)]",
505
+ "px-[var(--button-padding-x-sm,0.75rem)]",
506
+ "py-[var(--button-padding-y-sm,0.25rem)]",
507
+ "gap-1.5",
508
+ "has-[>svg]:px-[calc(var(--button-padding-x-sm,0.75rem)*0.83)]"
509
+ ].join(" "),
510
+ md: [
511
+ "h-[var(--button-height-md,2.25rem)]",
512
+ "px-[var(--button-padding-x-md,1rem)]",
513
+ "py-[var(--button-padding-y-md,0.5rem)]",
514
+ "has-[>svg]:px-[calc(var(--button-padding-x-md,1rem)*0.75)]"
515
+ ].join(" "),
516
+ lg: [
517
+ "h-[var(--button-height-lg,2.5rem)]",
518
+ "px-[var(--button-padding-x-lg,1.5rem)]",
519
+ "py-[var(--button-padding-y-lg,0.5rem)]",
520
+ "has-[>svg]:px-[calc(var(--button-padding-x-lg,1.5rem)*0.67)]"
521
+ ].join(" "),
522
+ icon: "size-[var(--button-height-md,2.25rem)]",
523
+ "icon-sm": "size-[var(--button-height-sm,2rem)]",
524
+ "icon-lg": "size-[var(--button-height-lg,2.5rem)]"
421
525
  }
422
- return /* @__PURE__ */ jsx(
423
- "span",
424
- {
425
- ref,
426
- ...commonProps,
427
- children
428
- }
429
- );
526
+ },
527
+ defaultVariants: {
528
+ variant: "default",
529
+ size: "default"
430
530
  }
431
- );
432
- Pressable.displayName = "Pressable";
433
- var maxWidthStyles = {
434
- sm: "max-w-screen-sm",
435
- md: "max-w-screen-md",
436
- lg: "max-w-screen-lg",
437
- xl: "max-w-7xl",
438
- "2xl": "max-w-screen-2xl",
439
- "4xl": "max-w-[1536px]",
440
- full: "max-w-full"
441
- };
442
- var Container = React__default.forwardRef(
443
- ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
444
- const Component = as;
445
- return /* @__PURE__ */ jsx(
446
- Component,
447
- {
448
- ref,
449
- className: cn(
450
- "mx-auto w-full px-2 sm:px-4 lg:px-8",
451
- maxWidthStyles[maxWidth],
452
- className
453
- ),
454
- ...props,
455
- children
531
+ });
532
+ var badgeVariants = cva(
533
+ "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
534
+ {
535
+ variants: {
536
+ variant: {
537
+ default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
538
+ secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
539
+ destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
540
+ outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
456
541
  }
457
- );
542
+ },
543
+ defaultVariants: {
544
+ variant: "default"
545
+ }
458
546
  }
459
547
  );
460
- Container.displayName = "Container";
461
-
462
- // lib/patternSvgs.ts
463
- var patternSvgs = {
464
- squareAltGrid: "https://cdn.ing/assets/files/record/286187/4gpn0yq2ptra8iwlvmwwv860ggwv",
465
- grid1: "https://cdn.ing/assets/files/record/286186/nbdflpgp4ostrno079hygibsflp3",
466
- noise: "https://cdn.ing/assets/i/r/286188/zrqcp9hynh3j7p2laihwzfbujgrl/noise.png",
467
- dots: "https://cdn.ing/assets/files/record/286198/yfsjx9thvtxzhl2qtshxyhkrm524",
468
- dotPattern: "https://cdn.ing/assets/files/record/286192/7ig0cku8aqbboiza8nuk6hw0nnsr",
469
- dotPattern2: "https://cdn.ing/assets/files/record/286189/arez6gd2s7isn9i1o6c7sexdq7bl",
470
- circles: "https://cdn.ing/assets/files/record/286190/gtmia3sncjtzetdshc20zf1d3c17",
471
- waves: "https://cdn.ing/assets/files/record/286191/mqlb33fzxz9cdth1bx7if0wmpkp1",
472
- crossPattern: "https://cdn.ing/assets/files/record/286193/9yfqwdbnqaipbp7fsb3wbzzmq472",
473
- architect: "https://cdn.ing/assets/files/record/286194/vgs88ugpvyhxu13wqgy0acvae6re",
474
- tinyCheckers: "https://cdn.ing/assets/files/record/286195/65efaknsw8kcpf9o3c2gybytsl5b",
475
- p6: "https://cdn.ing/assets/i/r/286196/6kl0rqnd6mjk8j7e525fo8fo0vkc/p6.webp"
476
- };
477
- var maskTop = "radial-gradient(ellipse 70% 60% at 50% 0%, #000 60%, transparent 100%)";
478
- var maskBottom = "radial-gradient(ellipse 100% 80% at 50% 100%, #000 50%, transparent 90%)";
479
- var maskCenter = "radial-gradient(ellipse 60% 60% at 50% 50%, #000 30%, transparent 70%)";
480
- var maskTopLeft = "radial-gradient(ellipse 80% 80% at 0% 0%, #000 50%, transparent 90%)";
481
- var maskTopRight = "radial-gradient(ellipse 80% 80% at 100% 0%, #000 50%, transparent 90%)";
482
- var maskBottomLeft = "radial-gradient(ellipse 80% 80% at 0% 100%, #000 50%, transparent 90%)";
483
- var maskBottomRight = "radial-gradient(ellipse 80% 80% at 100% 100%, #000 50%, transparent 90%)";
484
- var circuitBoardPattern = (id, mask) => /* @__PURE__ */ jsxs(
485
- "svg",
486
- {
487
- className: "h-full w-full",
488
- xmlns: "http://www.w3.org/2000/svg",
489
- style: mask ? {
490
- maskImage: mask,
491
- WebkitMaskImage: mask
492
- } : void 0,
493
- children: [
494
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
495
- "pattern",
496
- {
497
- id,
498
- x: "0",
499
- y: "0",
500
- width: "100",
501
- height: "100",
502
- patternUnits: "userSpaceOnUse",
503
- children: [
504
- /* @__PURE__ */ jsx(
505
- "path",
506
- {
507
- d: "M0 50h40M60 50h40M50 0v40M50 60v40",
508
- stroke: "hsl(var(--muted))",
509
- strokeWidth: "1",
510
- fill: "none"
511
- }
512
- ),
513
- /* @__PURE__ */ jsx("circle", { cx: "50", cy: "50", r: "3", fill: "hsl(var(--muted))" }),
514
- /* @__PURE__ */ jsx("circle", { cx: "0", cy: "50", r: "2", fill: "hsl(var(--muted))" }),
515
- /* @__PURE__ */ jsx("circle", { cx: "100", cy: "50", r: "2", fill: "hsl(var(--muted))" }),
516
- /* @__PURE__ */ jsx("circle", { cx: "50", cy: "0", r: "2", fill: "hsl(var(--muted))" }),
517
- /* @__PURE__ */ jsx("circle", { cx: "50", cy: "100", r: "2", fill: "hsl(var(--muted))" })
518
- ]
519
- }
520
- ) }),
521
- /* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: `url(#${id})` })
522
- ]
548
+ function Badge({
549
+ className,
550
+ variant,
551
+ asChild = false,
552
+ ...props
553
+ }) {
554
+ const Comp = asChild ? Slot : "span";
555
+ return /* @__PURE__ */ jsx(
556
+ Comp,
557
+ {
558
+ "data-slot": "badge",
559
+ className: cn(badgeVariants({ variant }), className),
560
+ ...props
561
+ }
562
+ );
563
+ }
564
+ function normalizePhoneNumber(input) {
565
+ const trimmed = input.trim();
566
+ if (trimmed.toLowerCase().startsWith("tel:")) {
567
+ return trimmed;
523
568
  }
524
- );
525
- var gridDotsPattern = (id, mask) => /* @__PURE__ */ jsxs(
526
- "svg",
527
- {
528
- className: "h-full w-full",
529
- xmlns: "http://www.w3.org/2000/svg",
530
- style: mask ? {
531
- maskImage: mask,
532
- WebkitMaskImage: mask
533
- } : void 0,
534
- children: [
535
- /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
536
- "pattern",
537
- {
538
- id,
539
- x: "0",
540
- y: "0",
541
- width: "40",
542
- height: "40",
543
- patternUnits: "userSpaceOnUse",
544
- children: [
545
- /* @__PURE__ */ jsx(
546
- "path",
547
- {
548
- d: "M0 20h40M20 0v40",
549
- stroke: "hsl(var(--muted))",
550
- strokeWidth: "0.5",
551
- fill: "none"
552
- }
553
- ),
554
- /* @__PURE__ */ jsx("circle", { cx: "20", cy: "20", r: "2", fill: "hsl(var(--muted))" })
555
- ]
556
- }
557
- ) }),
558
- /* @__PURE__ */ jsx("rect", { width: "100%", height: "100%", fill: `url(#${id})` })
559
- ]
569
+ const match = trimmed.match(/^[\s\+\-\(\)]*(\d[\d\s\-\(\)\.]*\d)[\s\-]*(x|ext\.?|extension)?[\s\-]*(\d+)?$/i);
570
+ if (match) {
571
+ const mainNumber = match[1].replace(/[\s\-\(\)\.]/g, "");
572
+ const extension = match[3];
573
+ const normalized = mainNumber.length >= 10 && !trimmed.startsWith("+") ? `+${mainNumber}` : mainNumber;
574
+ const withExtension = extension ? `${normalized};ext=${extension}` : normalized;
575
+ return `tel:${withExtension}`;
576
+ }
577
+ const cleaned = trimmed.replace(/[\s\-\(\)\.]/g, "");
578
+ return `tel:${cleaned}`;
579
+ }
580
+ function normalizeEmail(input) {
581
+ const trimmed = input.trim();
582
+ if (trimmed.toLowerCase().startsWith("mailto:")) {
583
+ return trimmed;
584
+ }
585
+ return `mailto:${trimmed}`;
586
+ }
587
+ function isEmail(input) {
588
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
589
+ return emailRegex.test(input.trim());
590
+ }
591
+ function isPhoneNumber(input) {
592
+ const trimmed = input.trim();
593
+ if (trimmed.toLowerCase().startsWith("tel:")) {
594
+ return true;
595
+ }
596
+ const phoneRegex = /^[\s\+\-\(\)]*\d[\d\s\-\(\)\.]*\d[\s\-]*(x|ext\.?|extension)?[\s\-]*\d*$/i;
597
+ return phoneRegex.test(trimmed);
598
+ }
599
+ function isInternalUrl(href) {
600
+ if (typeof window === "undefined") {
601
+ return href.startsWith("/") && !href.startsWith("//");
602
+ }
603
+ const trimmed = href.trim();
604
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
605
+ return true;
606
+ }
607
+ try {
608
+ const url = new URL(trimmed, window.location.href);
609
+ const currentOrigin = window.location.origin;
610
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
611
+ return normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin);
612
+ } catch {
613
+ return false;
614
+ }
615
+ }
616
+ function toRelativePath(href) {
617
+ if (typeof window === "undefined") {
618
+ return href;
560
619
  }
561
- );
562
- var gridPattern = (size, mask) => /* @__PURE__ */ jsx(
563
- "div",
564
- {
565
- className: "h-full w-full bg-[linear-gradient(to_right,_hsl(var(--muted))_1px,_transparent_1px),linear-gradient(to_bottom,_hsl(var(--muted))_1px,_transparent_1px)]",
566
- style: {
567
- backgroundSize: `${size}px ${size}px`,
568
- ...mask ? {
569
- maskImage: mask,
570
- WebkitMaskImage: mask
571
- } : {}
572
- }
620
+ const trimmed = href.trim();
621
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
622
+ return trimmed;
573
623
  }
574
- );
575
- var diagonalCrossPattern = (mask) => /* @__PURE__ */ jsx(
576
- "div",
577
- {
578
- className: "h-full w-full",
579
- style: {
580
- backgroundImage: "repeating-linear-gradient(45deg, transparent, transparent 32px, hsl(var(--muted)) 32px, hsl(var(--muted)) 33px), repeating-linear-gradient(135deg, transparent, transparent 32px, hsl(var(--muted)) 32px, hsl(var(--muted)) 33px)",
581
- ...mask ? {
582
- maskImage: mask,
583
- WebkitMaskImage: mask
584
- } : {}
624
+ try {
625
+ const url = new URL(trimmed, window.location.href);
626
+ const currentOrigin = window.location.origin;
627
+ const normalizeOrigin = (origin) => origin.replace(/^(https?:\/\/)(www\.)?/, "$1");
628
+ if (normalizeOrigin(url.origin) === normalizeOrigin(currentOrigin)) {
629
+ return url.pathname + url.search + url.hash;
585
630
  }
631
+ } catch {
586
632
  }
587
- );
588
- var dashedGridMaskBase = "repeating-linear-gradient(to right, black 0px, black 3px, transparent 3px, transparent 8px), repeating-linear-gradient(to bottom, black 0px, black 3px, transparent 3px, transparent 8px)";
589
- var dashedGridPattern = (fadeMask) => {
590
- const mask = fadeMask ? `${dashedGridMaskBase}, ${fadeMask}` : dashedGridMaskBase;
591
- return /* @__PURE__ */ jsx(
592
- "div",
593
- {
594
- className: "h-full w-full",
595
- style: {
596
- backgroundImage: "linear-gradient(to right, hsl(var(--muted)) 1px, transparent 1px), linear-gradient(to bottom, hsl(var(--muted)) 1px, transparent 1px)",
597
- backgroundSize: "20px 20px",
598
- backgroundPosition: "0 0, 0 0",
599
- maskImage: mask,
600
- WebkitMaskImage: mask,
601
- maskComposite: "intersect",
602
- WebkitMaskComposite: "source-in"
603
- }
633
+ return trimmed;
634
+ }
635
+ function useNavigation({
636
+ href,
637
+ onClick
638
+ } = {}) {
639
+ const linkType = React3.useMemo(() => {
640
+ if (!href || href.trim() === "") {
641
+ return onClick ? "none" : "none";
604
642
  }
605
- );
606
- };
607
- var gradientGlow = (position) => /* @__PURE__ */ jsx(
608
- "div",
609
- {
610
- className: cn(
611
- "pointer-events-none absolute left-1/2 z-0 aspect-square w-3/4 -translate-x-1/2 rounded-full opacity-50 blur-3xl",
612
- position === "top" ? "-top-1/4" : "-bottom-1/4"
613
- ),
614
- style: {
615
- background: "radial-gradient(circle, hsl(var(--primary)) 0%, transparent 70%)"
643
+ const trimmed = href.trim();
644
+ if (trimmed.toLowerCase().startsWith("mailto:") || isEmail(trimmed)) {
645
+ return "mailto";
616
646
  }
617
- }
618
- );
619
- var spotlight = (position) => /* @__PURE__ */ jsx(
620
- "div",
621
- {
622
- className: cn(
623
- "pointer-events-none absolute top-1/2 z-0 aspect-square w-3/4 -translate-y-1/2 rounded-full opacity-40 blur-3xl",
624
- position === "left" ? "-left-1/4" : "-right-1/4"
625
- ),
626
- style: {
627
- background: "radial-gradient(circle, hsl(var(--primary)) 0%, transparent 70%)"
647
+ if (trimmed.toLowerCase().startsWith("tel:") || isPhoneNumber(trimmed)) {
648
+ return "tel";
628
649
  }
629
- }
630
- );
631
- var patternOverlays = {
632
- circuitBoardBasic: () => circuitBoardPattern("circuit-board-basic"),
633
- circuitBoardFadeTop: () => circuitBoardPattern("circuit-board-fade-top", maskTop),
634
- circuitBoardFadeBottom: () => circuitBoardPattern("circuit-board-fade-bottom", maskBottom),
635
- circuitBoardFadeCenter: () => circuitBoardPattern("circuit-board-fade-center", maskCenter),
636
- circuitBoardFadeTopLeft: () => circuitBoardPattern("circuit-board-fade-top-left", maskTopLeft),
637
- circuitBoardFadeTopRight: () => circuitBoardPattern("circuit-board-fade-top-right", maskTopRight),
638
- circuitBoardFadeBottomLeft: () => circuitBoardPattern("circuit-board-fade-bottom-left", maskBottomLeft),
639
- circuitBoardFadeBottomRight: () => circuitBoardPattern("circuit-board-fade-bottom-right", maskBottomRight),
640
- dashedGridBasic: () => dashedGridPattern(),
641
- dashedGridFadeTop: () => dashedGridPattern(maskTop),
642
- dashedGridFadeBottom: () => dashedGridPattern(maskBottom),
643
- dashedGridFadeCenter: () => dashedGridPattern(maskCenter),
644
- dashedGridFadeTopLeft: () => dashedGridPattern(maskTopLeft),
645
- dashedGridFadeTopRight: () => dashedGridPattern(maskTopRight),
646
- dashedGridFadeBottomLeft: () => dashedGridPattern(maskBottomLeft),
647
- dashedGridFadeBottomRight: () => dashedGridPattern(maskBottomRight),
648
- diagonalCrossBasic: () => diagonalCrossPattern(),
649
- diagonalCrossFadeTop: () => diagonalCrossPattern(maskTop),
650
- diagonalCrossFadeBottom: () => diagonalCrossPattern(maskBottom),
651
- diagonalCrossFadeCenter: () => diagonalCrossPattern(maskCenter),
652
- diagonalCrossFadeTopLeft: () => diagonalCrossPattern(maskTopLeft),
653
- diagonalCrossFadeTopRight: () => diagonalCrossPattern(maskTopRight),
654
- diagonalCrossFadeBottomLeft: () => diagonalCrossPattern(maskBottomLeft),
655
- diagonalCrossFadeBottomRight: () => diagonalCrossPattern(maskBottomRight),
656
- gridBasic: () => gridPattern(40),
657
- gridFadeTop: () => gridPattern(32, maskTop),
658
- gridFadeBottom: () => gridPattern(32, maskBottom),
659
- gridFadeCenter: () => gridPattern(40, maskCenter),
660
- gridFadeTopLeft: () => gridPattern(32, maskTopLeft),
661
- gridFadeTopRight: () => gridPattern(32, maskTopRight),
662
- gridFadeBottomLeft: () => gridPattern(32, maskBottomLeft),
663
- gridFadeBottomRight: () => gridPattern(32, maskBottomRight),
664
- gridDotsBasic: () => gridDotsPattern("grid-dots-basic"),
665
- gridDotsFadeCenter: () => gridDotsPattern("grid-dots-fade-center", maskCenter),
666
- gradientGlowTop: () => gradientGlow("top"),
667
- gradientGlowBottom: () => gradientGlow("bottom"),
668
- spotlightLeft: () => spotlight("left"),
669
- spotlightRight: () => spotlight("right")
670
- };
671
- var inlinePatternStyles = {
672
- radialGradientTop: {
673
- background: "radial-gradient(125% 125% at 50% 10%, hsl(var(--background)) 40%, hsl(var(--primary)) 100%)"
674
- },
675
- radialGradientBottom: {
676
- background: "radial-gradient(125% 125% at 50% 90%, hsl(var(--background)) 40%, hsl(var(--primary)) 100%)"
677
- }
678
- };
679
- function PatternBackground({
680
- pattern,
681
- opacity = 0.08,
682
- className,
683
- style
684
- }) {
685
- if (!pattern) {
686
- return null;
687
- }
688
- if (pattern in inlinePatternStyles) {
689
- const inlineStyle = inlinePatternStyles[pattern];
690
- return /* @__PURE__ */ jsx(
691
- "div",
692
- {
693
- className: cn("pointer-events-none absolute inset-0 z-0", className),
694
- style: { ...inlineStyle, opacity, ...style },
695
- "aria-hidden": "true"
650
+ if (isInternalUrl(trimmed)) {
651
+ return "internal";
652
+ }
653
+ try {
654
+ new URL(trimmed, typeof window !== "undefined" ? window.location.href : "http://localhost");
655
+ return "external";
656
+ } catch {
657
+ return "internal";
658
+ }
659
+ }, [href, onClick]);
660
+ const normalizedHref = React3.useMemo(() => {
661
+ if (!href || href.trim() === "") {
662
+ return void 0;
663
+ }
664
+ const trimmed = href.trim();
665
+ switch (linkType) {
666
+ case "tel":
667
+ return normalizePhoneNumber(trimmed);
668
+ case "mailto":
669
+ return normalizeEmail(trimmed);
670
+ case "internal":
671
+ return toRelativePath(trimmed);
672
+ case "external":
673
+ return trimmed;
674
+ default:
675
+ return trimmed;
676
+ }
677
+ }, [href, linkType]);
678
+ const target = React3.useMemo(() => {
679
+ switch (linkType) {
680
+ case "external":
681
+ return "_blank";
682
+ case "internal":
683
+ return "_self";
684
+ case "mailto":
685
+ case "tel":
686
+ return void 0;
687
+ default:
688
+ return void 0;
689
+ }
690
+ }, [linkType]);
691
+ const rel = React3.useMemo(() => {
692
+ if (linkType === "external") {
693
+ return "noopener noreferrer";
694
+ }
695
+ return void 0;
696
+ }, [linkType]);
697
+ const isExternal = linkType === "external";
698
+ const isInternal = linkType === "internal";
699
+ const shouldUseRouter = isInternal && typeof normalizedHref === "string" && normalizedHref.startsWith("/");
700
+ const handleClick = React3.useCallback(
701
+ (event) => {
702
+ if (onClick) {
703
+ try {
704
+ onClick(event);
705
+ } catch (error) {
706
+ console.error("Error in user onClick handler:", error);
707
+ }
696
708
  }
697
- );
698
- }
699
- if (pattern in patternOverlays) {
700
- const Overlay = patternOverlays[pattern];
701
- return /* @__PURE__ */ jsx(
702
- "div",
703
- {
704
- className: cn("pointer-events-none absolute inset-0 z-0", className),
705
- style: { opacity, ...style },
706
- "aria-hidden": "true",
707
- children: Overlay()
709
+ if (event.defaultPrevented) {
710
+ return;
708
711
  }
709
- );
710
- }
711
- const patternUrl = pattern in patternSvgs ? patternSvgs[pattern] : pattern;
712
- return /* @__PURE__ */ jsx(
713
- "div",
714
- {
715
- className: cn("pointer-events-none absolute inset-0 z-0", className),
716
- style: {
717
- backgroundImage: `url(${patternUrl})`,
718
- backgroundRepeat: "repeat",
719
- backgroundSize: "auto",
720
- opacity,
721
- ...style
722
- },
723
- "aria-hidden": "true"
724
- }
712
+ if (shouldUseRouter && normalizedHref && event.button === 0 && // left-click only
713
+ !event.metaKey && !event.altKey && !event.ctrlKey && !event.shiftKey) {
714
+ if (typeof window !== "undefined") {
715
+ const handler = window.__opensiteNavigationHandler;
716
+ if (typeof handler === "function") {
717
+ try {
718
+ const handled = handler(normalizedHref, event.nativeEvent || event);
719
+ if (handled !== false) {
720
+ event.preventDefault();
721
+ }
722
+ } catch (error) {
723
+ console.error("Error in navigation handler:", error);
724
+ }
725
+ }
726
+ }
727
+ }
728
+ },
729
+ [onClick, shouldUseRouter, normalizedHref]
725
730
  );
731
+ return {
732
+ linkType,
733
+ normalizedHref,
734
+ target,
735
+ rel,
736
+ isExternal,
737
+ isInternal,
738
+ shouldUseRouter,
739
+ handleClick
740
+ };
726
741
  }
727
- var backgroundStyles = {
728
- default: "bg-background text-foreground",
729
- white: "bg-white text-dark",
730
- gray: "bg-muted/30 text-foreground",
731
- dark: "bg-foreground text-background",
732
- transparent: "bg-transparent text-foreground",
733
- gradient: "bg-linear-to-br from-primary via-primary/90 to-foreground text-primary-foreground",
734
- primary: "bg-primary text-primary-foreground",
735
- secondary: "bg-secondary text-secondary-foreground",
736
- muted: "bg-muted text-muted-foreground"
737
- };
738
- var spacingStyles = {
739
- none: "py-0 md:py-0",
740
- sm: "py-12 md:py-16",
741
- md: "py-16 md:py-24",
742
- lg: "py-20 md:py-32",
743
- xl: "py-24 md:py-40"
744
- };
745
- var predefinedSpacings = ["none", "sm", "md", "lg", "xl"];
746
- var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
747
- var Section = React__default.forwardRef(
742
+ var Pressable = React3.forwardRef(
748
743
  ({
749
- id,
750
- title,
751
- subtitle,
752
744
  children,
753
745
  className,
754
- style,
755
- background = "default",
756
- spacing = "lg",
757
- pattern,
758
- patternOpacity,
759
- patternClassName,
760
- containerClassName,
761
- containerMaxWidth = "xl",
746
+ href,
747
+ onClick,
748
+ variant,
749
+ size,
750
+ asButton = false,
751
+ fallbackComponentType = "span",
752
+ componentType,
753
+ "aria-label": ariaLabel,
754
+ "aria-describedby": ariaDescribedby,
755
+ id,
762
756
  ...props
763
757
  }, ref) => {
764
- const effectivePatternOpacity = patternOpacity !== void 0 ? patternOpacity : pattern ? 1 : 0;
765
- return /* @__PURE__ */ jsxs(
766
- "section",
758
+ const navigation = useNavigation({ href, onClick });
759
+ const {
760
+ normalizedHref,
761
+ target,
762
+ rel,
763
+ linkType,
764
+ isInternal,
765
+ handleClick
766
+ } = navigation;
767
+ const shouldRenderLink = normalizedHref && linkType !== "none";
768
+ const shouldRenderButton = !shouldRenderLink && onClick;
769
+ const effectiveComponentType = componentType || (shouldRenderLink ? "a" : shouldRenderButton ? "button" : fallbackComponentType);
770
+ const finalComponentType = isInternal && shouldRenderLink ? "a" : effectiveComponentType;
771
+ const shouldApplyButtonStyles = asButton || variant || size;
772
+ const combinedClassName = cn(
773
+ shouldApplyButtonStyles && buttonVariants({ variant, size }),
774
+ className
775
+ );
776
+ const dataProps = Object.fromEntries(
777
+ Object.entries(props).filter(([key]) => key.startsWith("data-"))
778
+ );
779
+ const buttonDataAttributes = shouldApplyButtonStyles ? {
780
+ "data-slot": "button",
781
+ "data-variant": variant ?? "default",
782
+ "data-size": size ?? "default"
783
+ } : {};
784
+ const commonProps = {
785
+ className: combinedClassName,
786
+ onClick: handleClick,
787
+ "aria-label": ariaLabel,
788
+ "aria-describedby": ariaDescribedby,
789
+ id,
790
+ ...dataProps,
791
+ ...buttonDataAttributes
792
+ };
793
+ if (finalComponentType === "a" && shouldRenderLink) {
794
+ return /* @__PURE__ */ jsx(
795
+ "a",
796
+ {
797
+ ref,
798
+ href: normalizedHref,
799
+ target,
800
+ rel,
801
+ ...commonProps,
802
+ ...props,
803
+ children
804
+ }
805
+ );
806
+ }
807
+ if (finalComponentType === "button") {
808
+ return /* @__PURE__ */ jsx(
809
+ "button",
810
+ {
811
+ ref,
812
+ type: props.type || "button",
813
+ ...commonProps,
814
+ ...props,
815
+ children
816
+ }
817
+ );
818
+ }
819
+ if (finalComponentType === "div") {
820
+ return /* @__PURE__ */ jsx(
821
+ "div",
822
+ {
823
+ ref,
824
+ ...commonProps,
825
+ children
826
+ }
827
+ );
828
+ }
829
+ return /* @__PURE__ */ jsx(
830
+ "span",
767
831
  {
768
832
  ref,
769
- id,
770
- className: cn(
771
- "relative",
772
- pattern ? "overflow-hidden" : null,
773
- backgroundStyles[background],
774
- isPredefinedSpacing(spacing) ? spacingStyles[spacing] : spacing,
775
- className
776
- ),
777
- style,
778
- ...props,
779
- children: [
780
- /* @__PURE__ */ jsx(
781
- PatternBackground,
782
- {
783
- pattern,
784
- opacity: effectivePatternOpacity,
785
- className: patternClassName
786
- }
787
- ),
788
- /* @__PURE__ */ jsxs(
789
- Container,
790
- {
791
- maxWidth: containerMaxWidth,
792
- className: cn("relative z-10", containerClassName),
793
- children: [
794
- (title || subtitle) && /* @__PURE__ */ jsxs("div", { className: "mb-6 text-center md:mb-16", children: [
795
- subtitle && /* @__PURE__ */ jsx("p", { className: "mb-2 text-sm font-semibold uppercase tracking-wider", children: subtitle }),
796
- title && /* @__PURE__ */ jsx("h2", { className: "text-3xl font-bold tracking-tight md:text-4xl lg:text-5xl", children: title })
797
- ] }),
798
- children
799
- ]
800
- }
801
- )
802
- ]
833
+ ...commonProps,
834
+ children
803
835
  }
804
836
  );
805
837
  }
806
838
  );
807
- Section.displayName = "Section";
808
- var badgeVariants = cva(
809
- "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
810
- {
811
- variants: {
812
- variant: {
813
- default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
814
- secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
815
- destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
816
- outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
817
- }
818
- },
819
- defaultVariants: {
820
- variant: "default"
821
- }
822
- }
823
- );
824
- function Badge({
825
- className,
826
- variant,
827
- asChild = false,
828
- ...props
839
+ Pressable.displayName = "Pressable";
840
+ var MOBILE_CLASSES = {
841
+ "fit-left": "items-start md:items-center",
842
+ "fit-center": "items-center",
843
+ "fit-right": "items-end md:items-center",
844
+ "full-left": "items-stretch md:items-center",
845
+ "full-center": "items-stretch md:items-center",
846
+ "full-right": "items-stretch md:items-center"
847
+ };
848
+ function BlockActions({
849
+ mobileConfig,
850
+ actionsClassName,
851
+ verticalSpacing = "mt-4 md:mt-8",
852
+ actions,
853
+ actionsSlot
829
854
  }) {
830
- const Comp = asChild ? Slot : "span";
831
- return /* @__PURE__ */ jsx(
832
- Comp,
833
- {
834
- "data-slot": "badge",
835
- className: cn(badgeVariants({ variant }), className),
836
- ...props
837
- }
855
+ const renderAction = React3.useCallback(
856
+ (action, idx) => {
857
+ const {
858
+ label,
859
+ icon,
860
+ iconAfter,
861
+ children,
862
+ href,
863
+ onClick,
864
+ className: actionClassName,
865
+ ...pressableProps
866
+ } = action;
867
+ return /* @__PURE__ */ jsx(
868
+ Pressable,
869
+ {
870
+ href,
871
+ onClick,
872
+ asButton: action.asButton || true,
873
+ className: actionClassName,
874
+ ...pressableProps,
875
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
876
+ icon,
877
+ label,
878
+ iconAfter
879
+ ] })
880
+ },
881
+ idx
882
+ );
883
+ },
884
+ []
838
885
  );
886
+ const width = mobileConfig?.width ?? "fit";
887
+ const position = mobileConfig?.position ?? "left";
888
+ const mobileLayoutClass = MOBILE_CLASSES[`${width}-${position}`];
889
+ if (actionsSlot) {
890
+ return /* @__PURE__ */ jsx("div", { children: actionsSlot });
891
+ } else if (actions && actions?.length > 0) {
892
+ return /* @__PURE__ */ jsx(
893
+ "div",
894
+ {
895
+ className: cn(
896
+ "flex flex-col md:flex-row flex-wrap gap-4",
897
+ mobileLayoutClass,
898
+ actionsClassName,
899
+ verticalSpacing
900
+ ),
901
+ children: actions.map((action, index) => renderAction(action, index))
902
+ }
903
+ );
904
+ } else {
905
+ return null;
906
+ }
839
907
  }
840
908
  function HeroStatsSocialProof({
841
909
  badge,
@@ -851,10 +919,10 @@ function HeroStatsSocialProof({
851
919
  statusCard,
852
920
  statusCardSlot,
853
921
  background,
854
- spacing = "pt-28 pb-8 md:pt-32 md:pb-32",
855
922
  pattern,
856
923
  patternOpacity,
857
924
  className,
925
+ spacing = "pt-28 pb-8 md:pt-32 md:pb-32",
858
926
  containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
859
927
  contentClassName,
860
928
  badgeClassName,
@@ -865,34 +933,6 @@ function HeroStatsSocialProof({
865
933
  imageClassName,
866
934
  optixFlowConfig
867
935
  }) {
868
- const renderActions = useMemo(() => {
869
- if (actionsSlot) return actionsSlot;
870
- if (!actions || actions.length === 0) return null;
871
- return actions.map((action, index) => {
872
- const {
873
- label,
874
- icon,
875
- iconAfter,
876
- children,
877
- className: actionClassName,
878
- ...pressableProps
879
- } = action;
880
- return /* @__PURE__ */ jsx(
881
- Pressable,
882
- {
883
- asButton: true,
884
- className: actionClassName,
885
- ...pressableProps,
886
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
887
- icon,
888
- label,
889
- iconAfter
890
- ] })
891
- },
892
- index
893
- );
894
- });
895
- }, [actionsSlot, actions]);
896
936
  const renderStats = useMemo(() => {
897
937
  if (statsSlot) return statsSlot;
898
938
  if (!stats || stats.length === 0) return null;
@@ -908,7 +948,9 @@ function HeroStatsSocialProof({
908
948
  "div",
909
949
  {
910
950
  className: cn(
911
- "absolute bottom-4 md:-bottom-6 left-4 right-4 md:right-auto md:-left-6 rounded-2xl max-w-md p-4 shadow-2xl bg-card text-card-foreground"
951
+ "absolute bottom-4 md:-bottom-6 left-4 right-4",
952
+ "md:right-auto md:-left-6 rounded-2xl max-w-md py-4 pl-4",
953
+ "pr-4 md:pr-6 shadow-2xl bg-card text-card-foreground"
912
954
  ),
913
955
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
914
956
  statusCard.icon && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 h-12 w-12 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg", children: statusCard.icon }),
@@ -946,20 +988,22 @@ function HeroStatsSocialProof({
946
988
  }
947
989
  ) : /* @__PURE__ */ jsx("div", { className: headingClassName, children: heading })),
948
990
  description && (typeof description === "string" ? /* @__PURE__ */ jsx("p", { className: cn("text-lg text-balance", descriptionClassName), children: description }) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: description })),
949
- (actionsSlot || actions && actions.length > 0) && /* @__PURE__ */ jsx(
950
- "div",
991
+ /* @__PURE__ */ jsx(
992
+ BlockActions,
951
993
  {
952
- className: cn(
953
- "flex flex-col gap-4 sm:flex-row",
954
- actionsClassName
955
- ),
956
- children: renderActions
994
+ actions,
995
+ actionsClassName,
996
+ actionsSlot,
997
+ mobileConfig: { position: "left", width: "fit" }
957
998
  }
958
999
  ),
959
1000
  (statsSlot || stats && stats.length > 0) && /* @__PURE__ */ jsx(
960
1001
  "div",
961
1002
  {
962
- className: cn("grid grid-cols-3 gap-8 pt-8", statsClassName),
1003
+ className: cn(
1004
+ "flex flex-wrap gap-4 md:gap-8 lg:gap-12 pt-8",
1005
+ statsClassName
1006
+ ),
963
1007
  children: renderStats
964
1008
  }
965
1009
  )