@mission-studio/puck 1.0.1 → 1.0.3

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.
@@ -0,0 +1,183 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type ColorValue = {
5
+ color: string;
6
+ opacity: number;
7
+ };
8
+ type CustomFieldProps<T> = {
9
+ value: T;
10
+ onChangeAction: (value: T) => void;
11
+ disabled?: boolean;
12
+ label: string;
13
+ };
14
+
15
+ type ThemeColors = {
16
+ primary: ColorValue;
17
+ secondary: ColorValue;
18
+ accent: ColorValue;
19
+ background: ColorValue;
20
+ foreground: ColorValue;
21
+ muted: ColorValue;
22
+ };
23
+ type ThemeTypography = {
24
+ fontFamily: {
25
+ heading: string;
26
+ body: string;
27
+ };
28
+ fontSize: {
29
+ base: string;
30
+ heading: string;
31
+ };
32
+ fontWeight: {
33
+ normal: number;
34
+ heading: number;
35
+ };
36
+ };
37
+ type ThemeSpacing = {
38
+ xs: number;
39
+ sm: number;
40
+ md: number;
41
+ lg: number;
42
+ xl: number;
43
+ };
44
+ type ThemeBorders = {
45
+ radiusSmall: number;
46
+ radiusMedium: number;
47
+ radiusLarge: number;
48
+ };
49
+ type ThemeShadows = {
50
+ small: string;
51
+ medium: string;
52
+ large: string;
53
+ };
54
+ type PageTheme = {
55
+ id: string;
56
+ name: string;
57
+ colors: ThemeColors;
58
+ typography: ThemeTypography;
59
+ spacing: ThemeSpacing;
60
+ borders: ThemeBorders;
61
+ shadows: ThemeShadows;
62
+ };
63
+ type ThemeColorKey = keyof ThemeColors;
64
+ type ThemeSpacingKey = keyof ThemeSpacing;
65
+ type ThemeBorderKey = keyof ThemeBorders;
66
+ type ThemeShadowKey = keyof ThemeShadows;
67
+ type ThemeableColorValue = {
68
+ useTheme: true;
69
+ themeKey: ThemeColorKey;
70
+ } | {
71
+ useTheme: false;
72
+ value: ColorValue;
73
+ };
74
+ type ThemeableSpacingValue = {
75
+ useTheme: true;
76
+ themeKey: ThemeSpacingKey;
77
+ } | {
78
+ useTheme: false;
79
+ value: number;
80
+ };
81
+ type ThemeableBorderRadiusValue = {
82
+ useTheme: true;
83
+ themeKey: ThemeBorderKey;
84
+ } | {
85
+ useTheme: false;
86
+ value: number;
87
+ };
88
+ type ThemeableShadowValue = {
89
+ useTheme: true;
90
+ themeKey: ThemeShadowKey;
91
+ } | {
92
+ useTheme: false;
93
+ value: string;
94
+ };
95
+
96
+ type ThemeContextValue = {
97
+ theme: PageTheme;
98
+ resolveColor: (key: ThemeColorKey) => ColorValue;
99
+ resolveSpacing: (key: ThemeSpacingKey) => number;
100
+ resolveBorderRadius: (key: ThemeBorderKey) => number;
101
+ resolveShadow: (key: ThemeShadowKey) => string;
102
+ };
103
+ declare function ThemeProvider({ theme, children, }: {
104
+ theme: PageTheme | null;
105
+ children: ReactNode;
106
+ }): react_jsx_runtime.JSX.Element;
107
+ declare function useTheme(): ThemeContextValue;
108
+
109
+ declare const DEFAULT_THEME: PageTheme;
110
+
111
+ type BorderRadiusPreset = {
112
+ label: string;
113
+ value: number;
114
+ };
115
+ declare const borderRadiusScale: BorderRadiusPreset[];
116
+ declare const getClosestBorderRadiusValue: (value: number) => number;
117
+ declare const getBorderRadiusCSS: (value: number) => string;
118
+
119
+ type ColorPreset = {
120
+ label: string;
121
+ value: string;
122
+ };
123
+ declare const neutralColors: ColorPreset[];
124
+ declare const allColorPresets: ColorPreset[];
125
+
126
+ type ShadowPreset = {
127
+ label: string;
128
+ value: string;
129
+ css: string;
130
+ };
131
+ declare const shadowPresets: ShadowPreset[];
132
+ declare const getShadowCSS: (value: string) => string;
133
+
134
+ type SpacingPreset = {
135
+ label: string;
136
+ value: number;
137
+ };
138
+ declare const spacingScale: SpacingPreset[];
139
+ declare const getClosestSpacingValue: (value: number) => number;
140
+
141
+ type FontFamilyPreset = {
142
+ label: string;
143
+ value: string;
144
+ };
145
+ type FontSizePreset = {
146
+ label: string;
147
+ value: string;
148
+ css: string;
149
+ };
150
+ type FontWeightPreset = {
151
+ label: string;
152
+ value: number;
153
+ };
154
+ declare const fontFamilies: FontFamilyPreset[];
155
+ declare const fontSizes: FontSizePreset[];
156
+ declare const fontWeights: FontWeightPreset[];
157
+ declare const getFontSizeCSS: (value: string) => string;
158
+
159
+ type EntryContent = Record<string, string | number | boolean | null>;
160
+ type Entry = {
161
+ id: string;
162
+ venture_id: string;
163
+ name: string;
164
+ content: EntryContent;
165
+ created_at: string | null;
166
+ updated_at: string | null;
167
+ };
168
+ type EntryBoundValue<T> = {
169
+ useEntry: true;
170
+ entryName: string;
171
+ fieldKey: string;
172
+ } | {
173
+ useEntry: false;
174
+ value: T;
175
+ };
176
+
177
+ type ResponsiveVisibility = {
178
+ mobile: boolean;
179
+ desktop: boolean;
180
+ };
181
+ declare function ResponsiveToggleField({ value, onChangeAction, disabled, label, }: CustomFieldProps<ResponsiveVisibility>): react_jsx_runtime.JSX.Element;
182
+
183
+ export { getBorderRadiusCSS as A, type BorderRadiusPreset as B, type ColorValue as C, DEFAULT_THEME as D, type Entry as E, type FontFamilyPreset as F, getClosestBorderRadiusValue as G, getClosestSpacingValue as H, getFontSizeCSS as I, getShadowCSS as J, neutralColors as K, shadowPresets as L, spacingScale as M, useTheme as N, type PageTheme as P, type ResponsiveVisibility as R, type ShadowPreset as S, type ThemeableColorValue as T, type EntryBoundValue as a, type ThemeColorKey as b, type CustomFieldProps as c, type ColorPreset as d, type EntryContent as e, type FontSizePreset as f, type FontWeightPreset as g, ResponsiveToggleField as h, type SpacingPreset as i, type ThemeBorderKey as j, type ThemeBorders as k, type ThemeColors as l, ThemeProvider as m, type ThemeShadowKey as n, type ThemeShadows as o, type ThemeSpacing as p, type ThemeSpacingKey as q, type ThemeTypography as r, type ThemeableBorderRadiusValue as s, type ThemeableShadowValue as t, type ThemeableSpacingValue as u, allColorPresets as v, borderRadiusScale as w, fontFamilies as x, fontSizes as y, fontWeights as z };
@@ -0,0 +1,183 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type ColorValue = {
5
+ color: string;
6
+ opacity: number;
7
+ };
8
+ type CustomFieldProps<T> = {
9
+ value: T;
10
+ onChangeAction: (value: T) => void;
11
+ disabled?: boolean;
12
+ label: string;
13
+ };
14
+
15
+ type ThemeColors = {
16
+ primary: ColorValue;
17
+ secondary: ColorValue;
18
+ accent: ColorValue;
19
+ background: ColorValue;
20
+ foreground: ColorValue;
21
+ muted: ColorValue;
22
+ };
23
+ type ThemeTypography = {
24
+ fontFamily: {
25
+ heading: string;
26
+ body: string;
27
+ };
28
+ fontSize: {
29
+ base: string;
30
+ heading: string;
31
+ };
32
+ fontWeight: {
33
+ normal: number;
34
+ heading: number;
35
+ };
36
+ };
37
+ type ThemeSpacing = {
38
+ xs: number;
39
+ sm: number;
40
+ md: number;
41
+ lg: number;
42
+ xl: number;
43
+ };
44
+ type ThemeBorders = {
45
+ radiusSmall: number;
46
+ radiusMedium: number;
47
+ radiusLarge: number;
48
+ };
49
+ type ThemeShadows = {
50
+ small: string;
51
+ medium: string;
52
+ large: string;
53
+ };
54
+ type PageTheme = {
55
+ id: string;
56
+ name: string;
57
+ colors: ThemeColors;
58
+ typography: ThemeTypography;
59
+ spacing: ThemeSpacing;
60
+ borders: ThemeBorders;
61
+ shadows: ThemeShadows;
62
+ };
63
+ type ThemeColorKey = keyof ThemeColors;
64
+ type ThemeSpacingKey = keyof ThemeSpacing;
65
+ type ThemeBorderKey = keyof ThemeBorders;
66
+ type ThemeShadowKey = keyof ThemeShadows;
67
+ type ThemeableColorValue = {
68
+ useTheme: true;
69
+ themeKey: ThemeColorKey;
70
+ } | {
71
+ useTheme: false;
72
+ value: ColorValue;
73
+ };
74
+ type ThemeableSpacingValue = {
75
+ useTheme: true;
76
+ themeKey: ThemeSpacingKey;
77
+ } | {
78
+ useTheme: false;
79
+ value: number;
80
+ };
81
+ type ThemeableBorderRadiusValue = {
82
+ useTheme: true;
83
+ themeKey: ThemeBorderKey;
84
+ } | {
85
+ useTheme: false;
86
+ value: number;
87
+ };
88
+ type ThemeableShadowValue = {
89
+ useTheme: true;
90
+ themeKey: ThemeShadowKey;
91
+ } | {
92
+ useTheme: false;
93
+ value: string;
94
+ };
95
+
96
+ type ThemeContextValue = {
97
+ theme: PageTheme;
98
+ resolveColor: (key: ThemeColorKey) => ColorValue;
99
+ resolveSpacing: (key: ThemeSpacingKey) => number;
100
+ resolveBorderRadius: (key: ThemeBorderKey) => number;
101
+ resolveShadow: (key: ThemeShadowKey) => string;
102
+ };
103
+ declare function ThemeProvider({ theme, children, }: {
104
+ theme: PageTheme | null;
105
+ children: ReactNode;
106
+ }): react_jsx_runtime.JSX.Element;
107
+ declare function useTheme(): ThemeContextValue;
108
+
109
+ declare const DEFAULT_THEME: PageTheme;
110
+
111
+ type BorderRadiusPreset = {
112
+ label: string;
113
+ value: number;
114
+ };
115
+ declare const borderRadiusScale: BorderRadiusPreset[];
116
+ declare const getClosestBorderRadiusValue: (value: number) => number;
117
+ declare const getBorderRadiusCSS: (value: number) => string;
118
+
119
+ type ColorPreset = {
120
+ label: string;
121
+ value: string;
122
+ };
123
+ declare const neutralColors: ColorPreset[];
124
+ declare const allColorPresets: ColorPreset[];
125
+
126
+ type ShadowPreset = {
127
+ label: string;
128
+ value: string;
129
+ css: string;
130
+ };
131
+ declare const shadowPresets: ShadowPreset[];
132
+ declare const getShadowCSS: (value: string) => string;
133
+
134
+ type SpacingPreset = {
135
+ label: string;
136
+ value: number;
137
+ };
138
+ declare const spacingScale: SpacingPreset[];
139
+ declare const getClosestSpacingValue: (value: number) => number;
140
+
141
+ type FontFamilyPreset = {
142
+ label: string;
143
+ value: string;
144
+ };
145
+ type FontSizePreset = {
146
+ label: string;
147
+ value: string;
148
+ css: string;
149
+ };
150
+ type FontWeightPreset = {
151
+ label: string;
152
+ value: number;
153
+ };
154
+ declare const fontFamilies: FontFamilyPreset[];
155
+ declare const fontSizes: FontSizePreset[];
156
+ declare const fontWeights: FontWeightPreset[];
157
+ declare const getFontSizeCSS: (value: string) => string;
158
+
159
+ type EntryContent = Record<string, string | number | boolean | null>;
160
+ type Entry = {
161
+ id: string;
162
+ venture_id: string;
163
+ name: string;
164
+ content: EntryContent;
165
+ created_at: string | null;
166
+ updated_at: string | null;
167
+ };
168
+ type EntryBoundValue<T> = {
169
+ useEntry: true;
170
+ entryName: string;
171
+ fieldKey: string;
172
+ } | {
173
+ useEntry: false;
174
+ value: T;
175
+ };
176
+
177
+ type ResponsiveVisibility = {
178
+ mobile: boolean;
179
+ desktop: boolean;
180
+ };
181
+ declare function ResponsiveToggleField({ value, onChangeAction, disabled, label, }: CustomFieldProps<ResponsiveVisibility>): react_jsx_runtime.JSX.Element;
182
+
183
+ export { getBorderRadiusCSS as A, type BorderRadiusPreset as B, type ColorValue as C, DEFAULT_THEME as D, type Entry as E, type FontFamilyPreset as F, getClosestBorderRadiusValue as G, getClosestSpacingValue as H, getFontSizeCSS as I, getShadowCSS as J, neutralColors as K, shadowPresets as L, spacingScale as M, useTheme as N, type PageTheme as P, type ResponsiveVisibility as R, type ShadowPreset as S, type ThemeableColorValue as T, type EntryBoundValue as a, type ThemeColorKey as b, type CustomFieldProps as c, type ColorPreset as d, type EntryContent as e, type FontSizePreset as f, type FontWeightPreset as g, ResponsiveToggleField as h, type SpacingPreset as i, type ThemeBorderKey as j, type ThemeBorders as k, type ThemeColors as l, ThemeProvider as m, type ThemeShadowKey as n, type ThemeShadows as o, type ThemeSpacing as p, type ThemeSpacingKey as q, type ThemeTypography as r, type ThemeableBorderRadiusValue as s, type ThemeableShadowValue as t, type ThemeableSpacingValue as u, allColorPresets as v, borderRadiusScale as w, fontFamilies as x, fontSizes as y, fontWeights as z };
@@ -158,21 +158,8 @@ function Paragraph({
158
158
  return /* @__PURE__ */ jsx2("p", { id, style, children: resolvedText });
159
159
  }
160
160
 
161
- // hooks/useGtmEvent.ts
162
- function useGtmEvent() {
163
- return (eventName, data) => {
164
- if (typeof window === "undefined") return;
165
- if (typeof window.gtag !== "function") {
166
- console.warn("GTM not initialized. Make sure @next/third-parties/google GoogleTagManager is added to your layout.");
167
- return;
168
- }
169
- const eventData = {
170
- event: eventName,
171
- ...data && { value: data }
172
- };
173
- window.gtag("event", eventName, data || {});
174
- };
175
- }
161
+ // components/page/Button.tsx
162
+ import { sendGTMEvent } from "@next/third-parties/google";
176
163
 
177
164
  // hooks/useUtmParams.ts
178
165
  import { useEffect, useState } from "react";
@@ -263,7 +250,6 @@ function Button({
263
250
  }) {
264
251
  const { resolveColor: resolveColor2 } = useTheme();
265
252
  const { getEntryValue } = useEntries();
266
- const sendEvent = useGtmEvent();
267
253
  const utm = useUtmParams();
268
254
  const resolvedText = (() => {
269
255
  if (!text) return "Button";
@@ -277,11 +263,14 @@ function Button({
277
263
  return "Button";
278
264
  })();
279
265
  const handleClick = () => {
280
- sendEvent("button_click", {
281
- text: resolvedText,
282
- href: href || void 0,
283
- variant,
284
- ...utm
266
+ sendGTMEvent({
267
+ event: "button_click",
268
+ value: {
269
+ text: resolvedText,
270
+ href: href || void 0,
271
+ variant,
272
+ ...utm
273
+ }
285
274
  });
286
275
  };
287
276
  const resolvedColor = (() => {
@@ -496,6 +485,7 @@ function Image({
496
485
 
497
486
  // components/page/ImageCarousel.tsx
498
487
  import { useState as useState2 } from "react";
488
+ import { sendGTMEvent as sendGTMEvent2 } from "@next/third-parties/google";
499
489
  import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
500
490
  var aspectRatioMap2 = {
501
491
  "16:9": "16 / 9",
@@ -524,6 +514,7 @@ function ImageCarousel({
524
514
  }) {
525
515
  const [currentIndex, setCurrentIndex] = useState2(0);
526
516
  const { resolveColor: resolveColor2 } = useTheme();
517
+ const utm = useUtmParams();
527
518
  const resolvedArrowColor = (() => {
528
519
  if (!arrowColor) return { color: "#FFFFFF", opacity: 100 };
529
520
  if (typeof arrowColor === "string")
@@ -544,13 +535,42 @@ function ImageCarousel({
544
535
  return resolveColor2("primary");
545
536
  })();
546
537
  const goToPrevious = () => {
547
- setCurrentIndex((prev) => prev === 0 ? images.length - 1 : prev - 1);
538
+ const newIndex = currentIndex === 0 ? images.length - 1 : currentIndex - 1;
539
+ setCurrentIndex(newIndex);
540
+ sendGTMEvent2({
541
+ event: "carousel_navigate",
542
+ value: {
543
+ direction: "previous",
544
+ slideIndex: newIndex,
545
+ totalSlides: images.length,
546
+ ...utm
547
+ }
548
+ });
548
549
  };
549
550
  const goToNext = () => {
550
- setCurrentIndex((prev) => prev === images.length - 1 ? 0 : prev + 1);
551
+ const newIndex = currentIndex === images.length - 1 ? 0 : currentIndex + 1;
552
+ setCurrentIndex(newIndex);
553
+ sendGTMEvent2({
554
+ event: "carousel_navigate",
555
+ value: {
556
+ direction: "next",
557
+ slideIndex: newIndex,
558
+ totalSlides: images.length,
559
+ ...utm
560
+ }
561
+ });
551
562
  };
552
563
  const goToSlide = (index) => {
553
564
  setCurrentIndex(index);
565
+ sendGTMEvent2({
566
+ event: "carousel_navigate",
567
+ value: {
568
+ direction: "direct",
569
+ slideIndex: index,
570
+ totalSlides: images.length,
571
+ ...utm
572
+ }
573
+ });
554
574
  };
555
575
  if (images.length === 0) {
556
576
  return /* @__PURE__ */ jsx5(
@@ -1643,6 +1663,7 @@ function FeatureGrid({
1643
1663
 
1644
1664
  // components/page/Footer.tsx
1645
1665
  import { Facebook, Instagram, Twitter } from "lucide-react";
1666
+ import { sendGTMEvent as sendGTMEvent3 } from "@next/third-parties/google";
1646
1667
  import { jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
1647
1668
  function Footer({
1648
1669
  logo,
@@ -1655,11 +1676,31 @@ function Footer({
1655
1676
  puck
1656
1677
  }) {
1657
1678
  const DropZone = puck?.renderDropZone;
1679
+ const utm = useUtmParams();
1680
+ const getSocialPlatform = (url) => {
1681
+ if (url.includes("facebook")) return "facebook";
1682
+ if (url.includes("instagram")) return "instagram";
1683
+ if (url.includes("twitter")) return "twitter";
1684
+ return "social";
1685
+ };
1686
+ const handleSocialClick = (url) => {
1687
+ const platform = getSocialPlatform(url);
1688
+ sendGTMEvent3({
1689
+ event: "social_click",
1690
+ value: {
1691
+ platform,
1692
+ url,
1693
+ ...utm
1694
+ }
1695
+ });
1696
+ };
1658
1697
  const socialLinks = [
1659
1698
  { url: facebookUrl, Icon: Facebook },
1660
1699
  { url: instagramUrl, Icon: Instagram },
1661
1700
  { url: twitterUrl, Icon: Twitter }
1662
- ].filter((link) => link.url);
1701
+ ].filter(
1702
+ (link) => !!link.url
1703
+ );
1663
1704
  return /* @__PURE__ */ jsx18(
1664
1705
  "footer",
1665
1706
  {
@@ -1675,6 +1716,7 @@ function Footer({
1675
1716
  target: "_blank",
1676
1717
  rel: "noopener noreferrer",
1677
1718
  className: "transition-opacity hover:opacity-80",
1719
+ onClick: () => handleSocialClick(url),
1678
1720
  children: /* @__PURE__ */ jsx18(Icon3, { size: 24, style: { color: textColor } })
1679
1721
  },
1680
1722
  index
@@ -1689,6 +1731,7 @@ function Footer({
1689
1731
  import { useState as useState3 } from "react";
1690
1732
  import Link from "next/link";
1691
1733
  import { Menu, X } from "lucide-react";
1734
+ import { sendGTMEvent as sendGTMEvent4 } from "@next/third-parties/google";
1692
1735
  import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
1693
1736
  function Topbar({
1694
1737
  logo,
@@ -1701,6 +1744,29 @@ function Topbar({
1701
1744
  }) {
1702
1745
  const DropZone = puck?.renderDropZone;
1703
1746
  const [mobileMenuOpen, setMobileMenuOpen] = useState3(false);
1747
+ const utm = useUtmParams();
1748
+ const handleNavClick = (item) => {
1749
+ sendGTMEvent4({
1750
+ event: "nav_click",
1751
+ value: {
1752
+ name: item.name,
1753
+ url: item.url,
1754
+ linkType: item.linkType || "internal",
1755
+ ...utm
1756
+ }
1757
+ });
1758
+ };
1759
+ const handleMobileMenuToggle = () => {
1760
+ const newState = !mobileMenuOpen;
1761
+ setMobileMenuOpen(newState);
1762
+ sendGTMEvent4({
1763
+ event: "mobile_menu_toggle",
1764
+ value: {
1765
+ open: newState,
1766
+ ...utm
1767
+ }
1768
+ });
1769
+ };
1704
1770
  const renderLink = (item, index) => {
1705
1771
  const className = "hover:opacity-80 transition-opacity";
1706
1772
  if (item.linkType === "external") {
@@ -1711,15 +1777,34 @@ function Topbar({
1711
1777
  target: "_blank",
1712
1778
  rel: "noopener noreferrer",
1713
1779
  className,
1780
+ onClick: () => handleNavClick(item),
1714
1781
  children: item.name
1715
1782
  },
1716
1783
  index
1717
1784
  );
1718
1785
  }
1719
1786
  if (item.linkType === "scrollTo") {
1720
- return /* @__PURE__ */ jsx19("a", { href: item.url, className, children: item.name }, index);
1787
+ return /* @__PURE__ */ jsx19(
1788
+ "a",
1789
+ {
1790
+ href: item.url,
1791
+ className,
1792
+ onClick: () => handleNavClick(item),
1793
+ children: item.name
1794
+ },
1795
+ index
1796
+ );
1721
1797
  }
1722
- return /* @__PURE__ */ jsx19(Link, { href: item.url, className, children: item.name }, index);
1798
+ return /* @__PURE__ */ jsx19(
1799
+ Link,
1800
+ {
1801
+ href: item.url,
1802
+ className,
1803
+ onClick: () => handleNavClick(item),
1804
+ children: item.name
1805
+ },
1806
+ index
1807
+ );
1723
1808
  };
1724
1809
  return /* @__PURE__ */ jsxs8(
1725
1810
  "nav",
@@ -1733,19 +1818,28 @@ function Topbar({
1733
1818
  className: "mx-auto flex items-center justify-between",
1734
1819
  style: { maxWidth },
1735
1820
  children: [
1736
- /* @__PURE__ */ jsx19(Link, { href: logoUrl, className: "flex-shrink-0", children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" }) }),
1821
+ /* @__PURE__ */ jsx19(
1822
+ Link,
1823
+ {
1824
+ href: logoUrl,
1825
+ className: "flex-shrink-0",
1826
+ onClick: () => sendGTMEvent4({
1827
+ event: "nav_click",
1828
+ value: {
1829
+ name: "logo",
1830
+ url: logoUrl,
1831
+ linkType: "internal",
1832
+ ...utm
1833
+ }
1834
+ }),
1835
+ children: logo ? /* @__PURE__ */ jsx19("img", { src: logo, alt: "Logo", className: "h-8" }) : /* @__PURE__ */ jsx19("span", { className: "text-xl font-bold", children: "Logo" })
1836
+ }
1837
+ ),
1737
1838
  /* @__PURE__ */ jsxs8("div", { className: "hidden items-center gap-8 md:flex", children: [
1738
1839
  navItems.map(renderLink),
1739
1840
  DropZone && /* @__PURE__ */ jsx19(DropZone, { zone: "cta" })
1740
1841
  ] }),
1741
- /* @__PURE__ */ jsx19(
1742
- "button",
1743
- {
1744
- className: "md:hidden",
1745
- onClick: () => setMobileMenuOpen(!mobileMenuOpen),
1746
- children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 })
1747
- }
1748
- )
1842
+ /* @__PURE__ */ jsx19("button", { className: "md:hidden", onClick: handleMobileMenuToggle, children: mobileMenuOpen ? /* @__PURE__ */ jsx19(X, { size: 24 }) : /* @__PURE__ */ jsx19(Menu, { size: 24 }) })
1749
1843
  ]
1750
1844
  }
1751
1845
  ),
@@ -1768,6 +1862,7 @@ function Topbar({
1768
1862
  // components/page/Popup.tsx
1769
1863
  import { useState as useState4 } from "react";
1770
1864
  import { icons as icons4, X as X2 } from "lucide-react";
1865
+ import { sendGTMEvent as sendGTMEvent5 } from "@next/third-parties/google";
1771
1866
  import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
1772
1867
  function Icon2({ name, ...props }) {
1773
1868
  const formatted = name.charAt(0).toUpperCase() + name.slice(1);
@@ -1797,10 +1892,29 @@ function Popup({
1797
1892
  puck
1798
1893
  }) {
1799
1894
  const [isOpen, setIsOpen] = useState4(false);
1895
+ const utm = useUtmParams();
1896
+ const handleOpen = () => {
1897
+ setIsOpen(true);
1898
+ sendGTMEvent5({
1899
+ event: "popup_open",
1900
+ value: {
1901
+ ctaText,
1902
+ type: textLink ? "link" : "button",
1903
+ ...utm
1904
+ }
1905
+ });
1906
+ };
1907
+ const handleClose = () => {
1908
+ setIsOpen(false);
1909
+ sendGTMEvent5({
1910
+ event: "popup_close",
1911
+ value: { ctaText, ...utm }
1912
+ });
1913
+ };
1800
1914
  const trigger = textLink ? /* @__PURE__ */ jsx20(
1801
1915
  "button",
1802
1916
  {
1803
- onClick: () => setIsOpen(true),
1917
+ onClick: handleOpen,
1804
1918
  className: "underline hover:opacity-80",
1805
1919
  style: { color: buttonColor },
1806
1920
  children: ctaText
@@ -1808,7 +1922,7 @@ function Popup({
1808
1922
  ) : /* @__PURE__ */ jsxs9(
1809
1923
  "button",
1810
1924
  {
1811
- onClick: () => setIsOpen(true),
1925
+ onClick: handleOpen,
1812
1926
  className: cn(
1813
1927
  "flex items-center gap-2 rounded-full font-medium",
1814
1928
  sizeMap8[size]
@@ -1827,7 +1941,7 @@ function Popup({
1827
1941
  "div",
1828
1942
  {
1829
1943
  className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4",
1830
- onClick: () => setIsOpen(false),
1944
+ onClick: handleClose,
1831
1945
  children: /* @__PURE__ */ jsxs9(
1832
1946
  "div",
1833
1947
  {
@@ -1840,7 +1954,7 @@ function Popup({
1840
1954
  /* @__PURE__ */ jsx20(
1841
1955
  "button",
1842
1956
  {
1843
- onClick: () => setIsOpen(false),
1957
+ onClick: handleClose,
1844
1958
  className: "absolute top-4 right-4 text-gray-500 hover:text-gray-700",
1845
1959
  children: /* @__PURE__ */ jsx20(X2, { size: 24 })
1846
1960
  }
@@ -1857,8 +1971,6 @@ function Popup({
1857
1971
  export {
1858
1972
  Heading,
1859
1973
  Paragraph,
1860
- useGtmEvent,
1861
- useUtmParams,
1862
1974
  Button,
1863
1975
  Image,
1864
1976
  ImageCarousel,