@neowhale/storefront 0.2.34 → 0.2.36

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.
@@ -2135,7 +2135,18 @@ function useReferral() {
2135
2135
  referredBy: status?.referred_by ?? null
2136
2136
  };
2137
2137
  }
2138
- var NUM_PATTERN = /(\$?[\d,]+\.?\d*[+★%]?)/g;
2138
+ function trackClick(tracking, label, url, position) {
2139
+ if (!tracking?.gatewayUrl || !tracking?.code) return;
2140
+ const body = JSON.stringify({ label, url, position });
2141
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
2142
+ navigator.sendBeacon(
2143
+ `${tracking.gatewayUrl}/q/${encodeURIComponent(tracking.code)}/click`,
2144
+ new Blob([body], { type: "application/json" })
2145
+ );
2146
+ }
2147
+ }
2148
+ var NUM_SPLIT = /(\$?[\d,]+\.?\d*[+★%]?)/g;
2149
+ var NUM_TEST = /^\$?[\d,]+\.?\d*[+★%]?$/;
2139
2150
  function easeOutQuart(t) {
2140
2151
  return 1 - Math.pow(1 - t, 4);
2141
2152
  }
@@ -2192,147 +2203,69 @@ function AnimatedNumber({ raw }) {
2192
2203
  ] });
2193
2204
  }
2194
2205
  function AnimatedText({ text }) {
2195
- const parts = text.split(NUM_PATTERN);
2206
+ const parts = text.split(NUM_SPLIT);
2196
2207
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: parts.map(
2197
- (part, i) => NUM_PATTERN.test(part) ? /* @__PURE__ */ jsxRuntime.jsx(AnimatedNumber, { raw: part }, i) : part
2208
+ (part, i) => NUM_TEST.test(part) ? /* @__PURE__ */ jsxRuntime.jsx(AnimatedNumber, { raw: part }, i) : part
2198
2209
  ) });
2199
2210
  }
2200
- function trackClick(tracking, label, url, position) {
2201
- if (!tracking?.gatewayUrl || !tracking?.code) return;
2202
- const body = JSON.stringify({ label, url, position });
2203
- if (typeof navigator !== "undefined" && navigator.sendBeacon) {
2204
- navigator.sendBeacon(
2205
- `${tracking.gatewayUrl}/q/${encodeURIComponent(tracking.code)}/click`,
2206
- new Blob([body], { type: "application/json" })
2207
- );
2208
- }
2209
- }
2210
- function SectionRenderer({
2211
- section,
2212
- data,
2213
- theme,
2214
- tracking,
2215
- onEvent
2216
- }) {
2217
- const [showCOA, setShowCOA] = react.useState(false);
2218
- const el = (() => {
2219
- switch (section.type) {
2220
- case "hero":
2221
- return /* @__PURE__ */ jsxRuntime.jsx(HeroSection, { section, theme, tracking, onEvent });
2222
- case "text":
2223
- return /* @__PURE__ */ jsxRuntime.jsx(TextSection, { section, theme });
2224
- case "image":
2225
- return /* @__PURE__ */ jsxRuntime.jsx(ImageSection, { section, theme });
2226
- case "video":
2227
- return /* @__PURE__ */ jsxRuntime.jsx(VideoSection, { section, theme });
2228
- case "gallery":
2229
- return /* @__PURE__ */ jsxRuntime.jsx(GallerySection, { section, theme });
2230
- case "cta":
2231
- return /* @__PURE__ */ jsxRuntime.jsx(CTASection, { section, theme, tracking, onEvent });
2232
- case "stats":
2233
- return /* @__PURE__ */ jsxRuntime.jsx(StatsSection, { section, theme });
2234
- case "product_card":
2235
- return /* @__PURE__ */ jsxRuntime.jsx(ProductCardSection, { section, data, theme, tracking });
2236
- case "coa_viewer":
2237
- return /* @__PURE__ */ jsxRuntime.jsx(COAViewerSection, { section, data, theme, onShowCOA: () => setShowCOA(true), tracking });
2238
- case "social_links":
2239
- return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
2240
- case "lead_capture":
2241
- return /* @__PURE__ */ jsxRuntime.jsx(LeadCaptureSection, { section, data, theme, onEvent });
2242
- case "divider":
2243
- return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
2244
- default:
2245
- return null;
2246
- }
2247
- })();
2248
- const sectionRef = react.useRef(null);
2249
- react.useEffect(() => {
2250
- const el2 = sectionRef.current;
2251
- if (!el2 || typeof IntersectionObserver === "undefined") return;
2252
- const obs = new IntersectionObserver(
2253
- ([entry]) => {
2254
- if (entry.isIntersecting) {
2255
- onEvent?.("section_view", {
2256
- section_id: section.id,
2257
- section_type: section.type
2258
- });
2259
- obs.disconnect();
2260
- }
2261
- },
2262
- { threshold: 0.5 }
2263
- );
2264
- obs.observe(el2);
2265
- return () => obs.disconnect();
2266
- }, [section.id, section.type, onEvent]);
2267
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: sectionRef, "data-section-id": section.id, "data-section-type": section.type, children: [
2268
- el,
2269
- showCOA && data?.coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
2270
- ] });
2271
- }
2272
2211
  function HeroSection({ section, theme, tracking, onEvent }) {
2273
2212
  const { title, subtitle, background_image, cta_text, cta_url } = section.content;
2274
- return /* @__PURE__ */ jsxRuntime.jsxs(
2275
- "div",
2276
- {
2277
- style: {
2278
- position: "relative",
2279
- minHeight: "60vh",
2280
- display: "flex",
2281
- flexDirection: "column",
2282
- justifyContent: "center",
2283
- alignItems: "center",
2284
- textAlign: "center",
2285
- padding: "3rem 1.5rem",
2286
- backgroundImage: background_image ? `url(${background_image})` : void 0,
2287
- backgroundSize: "cover",
2288
- backgroundPosition: "center"
2289
- },
2290
- children: [
2291
- background_image && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
2292
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
2293
- title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: {
2294
- fontSize: "clamp(2rem, 8vw, 3rem)",
2295
- fontWeight: 300,
2296
- fontFamily: theme.fontDisplay || "inherit",
2297
- margin: "0 0 1rem",
2298
- lineHeight: 1.15,
2299
- letterSpacing: "-0.02em",
2300
- color: theme.fg
2301
- }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: title }) }),
2302
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
2213
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2214
+ position: "relative",
2215
+ minHeight: "60vh",
2216
+ display: "flex",
2217
+ flexDirection: "column",
2218
+ justifyContent: "center",
2219
+ alignItems: "center",
2220
+ textAlign: "center",
2221
+ padding: "3rem 1.5rem",
2222
+ backgroundImage: background_image ? `url(${background_image})` : void 0,
2223
+ backgroundSize: "cover",
2224
+ backgroundPosition: "center"
2225
+ }, children: [
2226
+ background_image && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
2227
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
2228
+ title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: {
2229
+ fontSize: "clamp(2rem, 8vw, 3rem)",
2230
+ fontWeight: 300,
2231
+ fontFamily: theme.fontDisplay || "inherit",
2232
+ margin: "0 0 1rem",
2233
+ lineHeight: 1.15,
2234
+ letterSpacing: "-0.02em",
2235
+ color: theme.fg
2236
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: title }) }),
2237
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
2238
+ fontSize: "0.85rem",
2239
+ color: theme.accent,
2240
+ margin: "0 0 2rem",
2241
+ lineHeight: 1.6,
2242
+ textTransform: "uppercase",
2243
+ letterSpacing: "0.15em"
2244
+ }, children: subtitle }),
2245
+ cta_text && cta_url && /* @__PURE__ */ jsxRuntime.jsx(
2246
+ "a",
2247
+ {
2248
+ href: cta_url,
2249
+ onClick: () => {
2250
+ trackClick(tracking, cta_text, cta_url);
2251
+ onEvent?.("cta_click", { label: cta_text, url: cta_url });
2252
+ },
2253
+ style: {
2254
+ display: "inline-block",
2255
+ padding: "0.875rem 2rem",
2256
+ background: theme.fg,
2257
+ color: theme.bg,
2258
+ textDecoration: "none",
2303
2259
  fontSize: "0.85rem",
2304
- color: theme.accent,
2305
- margin: "0 0 2rem",
2306
- lineHeight: 1.6,
2307
- textTransform: "uppercase",
2308
- letterSpacing: "0.15em"
2309
- }, children: subtitle }),
2310
- cta_text && cta_url && /* @__PURE__ */ jsxRuntime.jsx(
2311
- "a",
2312
- {
2313
- href: cta_url,
2314
- onClick: () => {
2315
- trackClick(tracking, cta_text, cta_url);
2316
- onEvent?.("cta_click", { label: cta_text, url: cta_url });
2317
- },
2318
- style: {
2319
- display: "inline-block",
2320
- padding: "0.875rem 2rem",
2321
- background: theme.fg,
2322
- color: theme.bg,
2323
- textDecoration: "none",
2324
- fontSize: "0.85rem",
2325
- fontWeight: 500,
2326
- letterSpacing: "0.08em",
2327
- textTransform: "uppercase"
2328
- },
2329
- children: cta_text
2330
- }
2331
- )
2332
- ] })
2333
- ]
2334
- }
2335
- );
2260
+ fontWeight: 500,
2261
+ letterSpacing: "0.08em",
2262
+ textTransform: "uppercase"
2263
+ },
2264
+ children: cta_text
2265
+ }
2266
+ )
2267
+ ] })
2268
+ ] });
2336
2269
  }
2337
2270
  function TextSection({ section, theme }) {
2338
2271
  const { heading, body } = section.content;
@@ -2354,14 +2287,7 @@ function ImageSection({ section, theme }) {
2354
2287
  const contained = section.config?.contained !== false;
2355
2288
  if (!url) return null;
2356
2289
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: contained ? "1.5rem" : 0, maxWidth: contained ? 640 : void 0, margin: contained ? "0 auto" : void 0 }, children: [
2357
- /* @__PURE__ */ jsxRuntime.jsx(
2358
- "img",
2359
- {
2360
- src: url,
2361
- alt: alt || "",
2362
- style: { width: "100%", display: "block", objectFit: "cover" }
2363
- }
2364
- ),
2290
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: url, alt: alt || "", style: { width: "100%", display: "block", objectFit: "cover" } }),
2365
2291
  caption && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, textAlign: "center", marginTop: "0.75rem" }, children: caption })
2366
2292
  ] });
2367
2293
  }
@@ -2369,36 +2295,38 @@ function VideoSection({ section, theme }) {
2369
2295
  const { url, poster } = section.content;
2370
2296
  if (!url) return null;
2371
2297
  const isEmbed = url.includes("youtube") || url.includes("youtu.be") || url.includes("vimeo");
2372
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: isEmbed ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", paddingBottom: "56.25%", height: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
2373
- "iframe",
2374
- {
2375
- src: toEmbedUrl(url),
2376
- style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" },
2377
- allow: "autoplay; fullscreen",
2378
- title: "Video"
2379
- }
2380
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
2381
- "video",
2382
- {
2383
- src: url,
2384
- poster,
2385
- controls: true,
2386
- style: { width: "100%", display: "block", background: theme.surface }
2387
- }
2388
- ) });
2298
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: isEmbed ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "relative", paddingBottom: "56.25%", height: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: toEmbedUrl(url), style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" }, allow: "autoplay; fullscreen", title: "Video" }) }) : /* @__PURE__ */ jsxRuntime.jsx("video", { src: url, poster, controls: true, style: { width: "100%", display: "block", background: theme.surface } }) });
2389
2299
  }
2390
2300
  function GallerySection({ section, theme }) {
2391
2301
  const { images } = section.content;
2392
2302
  const columns = section.config?.columns || 3;
2393
2303
  if (!images || images.length === 0) return null;
2394
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 800, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: "0.5rem" }, children: images.map((img, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { aspectRatio: "1", overflow: "hidden", background: theme.surface }, children: /* @__PURE__ */ jsxRuntime.jsx(
2395
- "img",
2304
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 800, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: "0.5rem" }, children: images.map((img, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: { aspectRatio: "1", overflow: "hidden", background: theme.surface }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: img.url, alt: img.alt || "", style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }, i)) }) });
2305
+ }
2306
+ function SocialLinksSection({ section, theme }) {
2307
+ const { links } = section.content;
2308
+ if (!links || links.length === 0) return null;
2309
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center", gap: "1.5rem", flexWrap: "wrap" }, children: links.map((link, i) => /* @__PURE__ */ jsxRuntime.jsx(
2310
+ "a",
2396
2311
  {
2397
- src: img.url,
2398
- alt: img.alt || "",
2399
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
2400
- }
2401
- ) }, i)) }) });
2312
+ href: link.url,
2313
+ target: "_blank",
2314
+ rel: "noopener noreferrer",
2315
+ style: { color: theme.muted, textDecoration: "none", fontSize: "0.85rem", fontWeight: 500, textTransform: "capitalize", letterSpacing: "0.03em" },
2316
+ children: link.platform
2317
+ },
2318
+ i
2319
+ )) });
2320
+ }
2321
+ function DividerSection({ theme }) {
2322
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1rem 1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } }) });
2323
+ }
2324
+ function toEmbedUrl(url) {
2325
+ const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
2326
+ if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
2327
+ const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
2328
+ if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
2329
+ return url;
2402
2330
  }
2403
2331
  function CTASection({ section, theme, tracking, onEvent }) {
2404
2332
  const { title, subtitle, buttons } = section.content;
@@ -2461,29 +2389,15 @@ function StatsSection({ section, theme }) {
2461
2389
  if (!stats || stats.length === 0) return null;
2462
2390
  if (layout === "list") {
2463
2391
  return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2464
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2465
- display: "flex",
2466
- justifyContent: "space-between",
2467
- alignItems: "baseline",
2468
- padding: "0.625rem 0"
2469
- }, children: [
2470
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2471
- fontSize: 12,
2472
- textTransform: "uppercase",
2473
- letterSpacing: "0.15em",
2474
- color: `${theme.fg}66`
2475
- }, children: stat.label }),
2392
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "baseline", padding: "0.625rem 0" }, children: [
2393
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 12, textTransform: "uppercase", letterSpacing: "0.15em", color: `${theme.fg}66` }, children: stat.label }),
2476
2394
  /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: stat.value }) })
2477
2395
  ] }),
2478
2396
  i < stats.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } })
2479
2397
  ] }, i)) });
2480
2398
  }
2481
2399
  const columns = Math.min(stats.length, 4);
2482
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2483
- display: "grid",
2484
- gridTemplateColumns: `repeat(${columns}, 1fr)`,
2485
- border: `1px solid ${theme.fg}0F`
2486
- }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2400
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, border: `1px solid ${theme.fg}0F` }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2487
2401
  padding: "1.25rem 0.5rem",
2488
2402
  textAlign: "center",
2489
2403
  borderRight: i < stats.length - 1 ? `1px solid ${theme.fg}0F` : void 0
@@ -2542,13 +2456,7 @@ function ProductCardSection({ section, data, theme, tracking }) {
2542
2456
  ] })
2543
2457
  ] }) });
2544
2458
  }
2545
- function COAViewerSection({
2546
- section,
2547
- data,
2548
- theme,
2549
- onShowCOA,
2550
- tracking
2551
- }) {
2459
+ function COAViewerSection({ section, data, theme, onShowCOA, tracking }) {
2552
2460
  const coa = data?.coa;
2553
2461
  const c = section.content;
2554
2462
  if (!coa) return null;
@@ -2587,6 +2495,28 @@ function COAViewerSection({
2587
2495
  onShowCOA();
2588
2496
  }, style: buttonStyle, children: buttonLabel }) });
2589
2497
  }
2498
+ function COAModal({ coa, theme, onClose }) {
2499
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column" }, children: [
2500
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2501
+ display: "flex",
2502
+ justifyContent: "space-between",
2503
+ alignItems: "center",
2504
+ padding: "0.75rem 1rem",
2505
+ borderBottom: `1px solid ${theme.fg}10`
2506
+ }, children: [
2507
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fff", fontWeight: 500, fontSize: "0.85rem" }, children: coa.document_name || "Lab Results" }),
2508
+ /* @__PURE__ */ jsxRuntime.jsx("button", { onClick: onClose, style: {
2509
+ background: `${theme.fg}10`,
2510
+ border: "none",
2511
+ color: "#fff",
2512
+ fontSize: "0.85rem",
2513
+ cursor: "pointer",
2514
+ padding: "0.375rem 0.75rem"
2515
+ }, children: "Close" })
2516
+ ] }),
2517
+ /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
2518
+ ] });
2519
+ }
2590
2520
  function LeadCaptureSection({ section, data, theme, onEvent }) {
2591
2521
  const c = section.content;
2592
2522
  const [firstName, setFirstName] = react.useState("");
@@ -2597,13 +2527,17 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2597
2527
  const gatewayUrl = c.gateway_url || data.gatewayUrl || "https://whale-gateway.fly.dev";
2598
2528
  const storeId = c.store_id || data.store?.id;
2599
2529
  const slug = c.landing_page_slug || data.landing_page?.slug;
2530
+ const heading = c.heading || "get 10% off your first visit.";
2531
+ const subtitle = c.subtitle || "drop your email and we will send you the code.";
2532
+ const buttonText = c.button_text || "Claim My Discount";
2533
+ const successHeading = c.success_heading || "You\u2019re in!";
2534
+ const successMessage = c.success_message || "Check your inbox for the discount code.";
2600
2535
  async function handleSubmit(e) {
2601
2536
  e.preventDefault();
2602
2537
  if (!email || !storeId) return;
2603
2538
  setStatus("loading");
2604
2539
  setErrorMsg("");
2605
2540
  const urlParams = typeof window !== "undefined" ? new URLSearchParams(window.location.search) : null;
2606
- const analyticsData = data.analyticsContext;
2607
2541
  try {
2608
2542
  const res = await fetch(`${gatewayUrl}/v1/stores/${storeId}/storefront/leads`, {
2609
2543
  method: "POST",
@@ -2619,8 +2553,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2619
2553
  if (newsletterOptIn) t.push(c.newsletter_tag || "newsletter-subscriber");
2620
2554
  return t.length > 0 ? t : void 0;
2621
2555
  })(),
2622
- visitor_id: analyticsData?.visitorId || void 0,
2623
- session_id: analyticsData?.sessionId || void 0,
2556
+ visitor_id: data.analyticsContext?.visitorId || void 0,
2557
+ session_id: data.analyticsContext?.sessionId || void 0,
2624
2558
  utm_source: urlParams?.get("utm_source") || void 0,
2625
2559
  utm_medium: urlParams?.get("utm_medium") || void 0,
2626
2560
  utm_campaign: urlParams?.get("utm_campaign") || void 0,
@@ -2638,11 +2572,6 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2638
2572
  setStatus("error");
2639
2573
  }
2640
2574
  }
2641
- const heading = c.heading || "get 10% off your first visit.";
2642
- const subtitle = c.subtitle || "drop your email and we will send you the code.";
2643
- const buttonText = c.button_text || "Claim My Discount";
2644
- const successHeading = c.success_heading || "You\u2019re in!";
2645
- const successMessage = c.success_message || "Check your inbox for the discount code.";
2646
2575
  const inputStyle = {
2647
2576
  flex: 1,
2648
2577
  minWidth: 0,
@@ -2657,41 +2586,10 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2657
2586
  fontFamily: "inherit",
2658
2587
  transition: "border-color 0.2s"
2659
2588
  };
2589
+ if (status === "success") return /* @__PURE__ */ jsxRuntime.jsx(SuccessState, { theme, heading: successHeading, message: successMessage, couponCode: c.coupon_code });
2660
2590
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: [
2661
2591
  /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes lc-spin { to { transform: rotate(360deg) } }` }),
2662
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2663
- background: theme.surface,
2664
- border: `1px solid ${theme.fg}12`,
2665
- padding: "clamp(2rem, 6vw, 3rem)"
2666
- }, children: status === "success" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center" }, children: [
2667
- /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2668
- fontSize: "clamp(1.5rem, 5vw, 2rem)",
2669
- fontWeight: 300,
2670
- fontFamily: theme.fontDisplay || "inherit",
2671
- margin: "0 0 0.75rem",
2672
- lineHeight: 1.2,
2673
- letterSpacing: "-0.02em",
2674
- color: theme.fg
2675
- }, children: successHeading }),
2676
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
2677
- fontSize: "0.9rem",
2678
- color: `${theme.fg}99`,
2679
- margin: "0 0 1.5rem",
2680
- lineHeight: 1.6,
2681
- fontWeight: 300
2682
- }, children: successMessage }),
2683
- c.coupon_code && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2684
- display: "inline-block",
2685
- padding: "0.75rem 2rem",
2686
- background: `${theme.fg}08`,
2687
- border: `1px dashed ${theme.fg}30`,
2688
- fontSize: "clamp(1.25rem, 4vw, 1.75rem)",
2689
- fontWeight: 500,
2690
- fontFamily: "monospace",
2691
- letterSpacing: "0.12em",
2692
- color: theme.accent
2693
- }, children: c.coupon_code })
2694
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)" }, children: [
2695
2593
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", marginBottom: "clamp(1.5rem, 4vw, 2rem)" }, children: [
2696
2594
  /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2697
2595
  fontSize: "clamp(1.5rem, 5vw, 2.25rem)",
@@ -2713,27 +2611,8 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2713
2611
  ] }),
2714
2612
  /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, style: { display: "flex", flexDirection: "column", gap: "0.75rem" }, children: [
2715
2613
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: "0.75rem", flexWrap: "wrap" }, children: [
2716
- /* @__PURE__ */ jsxRuntime.jsx(
2717
- "input",
2718
- {
2719
- type: "text",
2720
- placeholder: "First name",
2721
- value: firstName,
2722
- onChange: (e) => setFirstName(e.target.value),
2723
- style: inputStyle
2724
- }
2725
- ),
2726
- /* @__PURE__ */ jsxRuntime.jsx(
2727
- "input",
2728
- {
2729
- type: "email",
2730
- placeholder: "Email address",
2731
- value: email,
2732
- onChange: (e) => setEmail(e.target.value),
2733
- required: true,
2734
- style: inputStyle
2735
- }
2736
- )
2614
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "text", placeholder: "First name", value: firstName, onChange: (e) => setFirstName(e.target.value), style: inputStyle }),
2615
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "email", placeholder: "Email address", value: email, onChange: (e) => setEmail(e.target.value), required: true, style: inputStyle })
2737
2616
  ] }),
2738
2617
  c.show_newsletter_opt_in !== false && /* @__PURE__ */ jsxRuntime.jsxs("label", { style: {
2739
2618
  display: "flex",
@@ -2751,121 +2630,130 @@ function LeadCaptureSection({ section, data, theme, onEvent }) {
2751
2630
  type: "checkbox",
2752
2631
  checked: newsletterOptIn,
2753
2632
  onChange: (e) => setNewsletterOptIn(e.target.checked),
2754
- style: {
2755
- width: 16,
2756
- height: 16,
2757
- accentColor: theme.accent,
2758
- cursor: "pointer",
2759
- flexShrink: 0
2760
- }
2633
+ style: { width: 16, height: 16, accentColor: theme.accent, cursor: "pointer", flexShrink: 0 }
2761
2634
  }
2762
2635
  ),
2763
2636
  c.newsletter_label || "Also sign me up for the newsletter \u2014 new drops, deals, and company news."
2764
2637
  ] }),
2765
2638
  status === "error" && errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: "#e55", margin: 0, fontWeight: 400 }, children: errorMsg }),
2766
- /* @__PURE__ */ jsxRuntime.jsxs(
2767
- "button",
2768
- {
2769
- type: "submit",
2770
- disabled: status === "loading",
2771
- style: {
2772
- width: "100%",
2773
- padding: "0.875rem",
2774
- background: theme.fg,
2775
- color: theme.bg,
2776
- border: "none",
2777
- fontSize: "0.85rem",
2778
- fontWeight: 500,
2779
- cursor: status === "loading" ? "wait" : "pointer",
2780
- letterSpacing: "0.08em",
2781
- textTransform: "uppercase",
2782
- fontFamily: "inherit",
2783
- display: "flex",
2784
- alignItems: "center",
2785
- justifyContent: "center",
2786
- gap: "0.5rem",
2787
- opacity: status === "loading" ? 0.7 : 1,
2788
- transition: "opacity 0.2s"
2789
- },
2790
- children: [
2791
- status === "loading" && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2792
- display: "inline-block",
2793
- width: 16,
2794
- height: 16,
2795
- border: `2px solid ${theme.bg}40`,
2796
- borderTopColor: theme.bg,
2797
- borderRadius: "50%",
2798
- animation: "lc-spin 0.8s linear infinite"
2799
- } }),
2800
- buttonText
2801
- ]
2802
- }
2803
- )
2639
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "submit", disabled: status === "loading", style: {
2640
+ width: "100%",
2641
+ padding: "0.875rem",
2642
+ background: theme.fg,
2643
+ color: theme.bg,
2644
+ border: "none",
2645
+ fontSize: "0.85rem",
2646
+ fontWeight: 500,
2647
+ cursor: status === "loading" ? "wait" : "pointer",
2648
+ letterSpacing: "0.08em",
2649
+ textTransform: "uppercase",
2650
+ fontFamily: "inherit",
2651
+ display: "flex",
2652
+ alignItems: "center",
2653
+ justifyContent: "center",
2654
+ gap: "0.5rem",
2655
+ opacity: status === "loading" ? 0.7 : 1,
2656
+ transition: "opacity 0.2s"
2657
+ }, children: [
2658
+ status === "loading" && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2659
+ display: "inline-block",
2660
+ width: 16,
2661
+ height: 16,
2662
+ border: `2px solid ${theme.bg}40`,
2663
+ borderTopColor: theme.bg,
2664
+ borderRadius: "50%",
2665
+ animation: "lc-spin 0.8s linear infinite"
2666
+ } }),
2667
+ buttonText
2668
+ ] })
2804
2669
  ] })
2805
- ] }) })
2670
+ ] })
2806
2671
  ] });
2807
2672
  }
2808
- function SocialLinksSection({ section, theme }) {
2809
- const { links } = section.content;
2810
- if (!links || links.length === 0) return null;
2811
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center", gap: "1.5rem", flexWrap: "wrap" }, children: links.map((link, i) => /* @__PURE__ */ jsxRuntime.jsx(
2812
- "a",
2813
- {
2814
- href: link.url,
2815
- target: "_blank",
2816
- rel: "noopener noreferrer",
2817
- style: {
2818
- color: theme.muted,
2819
- textDecoration: "none",
2820
- fontSize: "0.85rem",
2821
- fontWeight: 500,
2822
- textTransform: "capitalize",
2823
- letterSpacing: "0.03em"
2824
- },
2825
- children: link.platform
2826
- },
2827
- i
2828
- )) });
2829
- }
2830
- function DividerSection({ theme }) {
2831
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1rem 1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } }) });
2673
+ function SuccessState({ theme, heading, message, couponCode }) {
2674
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "3.5rem 1.5rem", maxWidth: 560, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}12`, padding: "clamp(2rem, 6vw, 3rem)", textAlign: "center" }, children: [
2675
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2676
+ fontSize: "clamp(1.5rem, 5vw, 2rem)",
2677
+ fontWeight: 300,
2678
+ fontFamily: theme.fontDisplay || "inherit",
2679
+ margin: "0 0 0.75rem",
2680
+ lineHeight: 1.2,
2681
+ letterSpacing: "-0.02em",
2682
+ color: theme.fg
2683
+ }, children: heading }),
2684
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9rem", color: `${theme.fg}99`, margin: "0 0 1.5rem", lineHeight: 1.6, fontWeight: 300 }, children: message }),
2685
+ couponCode && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2686
+ display: "inline-block",
2687
+ padding: "0.75rem 2rem",
2688
+ background: `${theme.fg}08`,
2689
+ border: `1px dashed ${theme.fg}30`,
2690
+ fontSize: "clamp(1.25rem, 4vw, 1.75rem)",
2691
+ fontWeight: 500,
2692
+ fontFamily: "monospace",
2693
+ letterSpacing: "0.12em",
2694
+ color: theme.accent
2695
+ }, children: couponCode })
2696
+ ] }) });
2832
2697
  }
2833
- function COAModal({ coa, theme, onClose }) {
2834
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column" }, children: [
2835
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2836
- display: "flex",
2837
- justifyContent: "space-between",
2838
- alignItems: "center",
2839
- padding: "0.75rem 1rem",
2840
- borderBottom: `1px solid ${theme.fg}10`
2841
- }, children: [
2842
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fff", fontWeight: 500, fontSize: "0.85rem" }, children: coa.document_name || "Lab Results" }),
2843
- /* @__PURE__ */ jsxRuntime.jsx(
2844
- "button",
2845
- {
2846
- onClick: onClose,
2847
- style: {
2848
- background: `${theme.fg}10`,
2849
- border: "none",
2850
- color: "#fff",
2851
- fontSize: "0.85rem",
2852
- cursor: "pointer",
2853
- padding: "0.375rem 0.75rem"
2854
- },
2855
- children: "Close"
2698
+ function SectionRenderer({
2699
+ section,
2700
+ data,
2701
+ theme,
2702
+ tracking,
2703
+ onEvent
2704
+ }) {
2705
+ const [showCOA, setShowCOA] = react.useState(false);
2706
+ const el = (() => {
2707
+ switch (section.type) {
2708
+ case "hero":
2709
+ return /* @__PURE__ */ jsxRuntime.jsx(HeroSection, { section, theme, tracking, onEvent });
2710
+ case "text":
2711
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSection, { section, theme });
2712
+ case "image":
2713
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageSection, { section, theme });
2714
+ case "video":
2715
+ return /* @__PURE__ */ jsxRuntime.jsx(VideoSection, { section, theme });
2716
+ case "gallery":
2717
+ return /* @__PURE__ */ jsxRuntime.jsx(GallerySection, { section, theme });
2718
+ case "cta":
2719
+ return /* @__PURE__ */ jsxRuntime.jsx(CTASection, { section, theme, tracking, onEvent });
2720
+ case "stats":
2721
+ return /* @__PURE__ */ jsxRuntime.jsx(StatsSection, { section, theme });
2722
+ case "product_card":
2723
+ return /* @__PURE__ */ jsxRuntime.jsx(ProductCardSection, { section, data, theme, tracking });
2724
+ case "coa_viewer":
2725
+ return /* @__PURE__ */ jsxRuntime.jsx(COAViewerSection, { section, data, theme, onShowCOA: () => setShowCOA(true), tracking });
2726
+ case "social_links":
2727
+ return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
2728
+ case "lead_capture":
2729
+ return /* @__PURE__ */ jsxRuntime.jsx(LeadCaptureSection, { section, data, theme, onEvent });
2730
+ case "divider":
2731
+ return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
2732
+ default:
2733
+ return null;
2734
+ }
2735
+ })();
2736
+ const sectionRef = react.useRef(null);
2737
+ react.useEffect(() => {
2738
+ const el2 = sectionRef.current;
2739
+ if (!el2 || typeof IntersectionObserver === "undefined") return;
2740
+ const obs = new IntersectionObserver(
2741
+ ([entry]) => {
2742
+ if (entry.isIntersecting) {
2743
+ onEvent?.("section_view", { section_id: section.id, section_type: section.type });
2744
+ obs.disconnect();
2856
2745
  }
2857
- )
2858
- ] }),
2859
- /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
2746
+ },
2747
+ { threshold: 0.5 }
2748
+ );
2749
+ obs.observe(el2);
2750
+ return () => obs.disconnect();
2751
+ }, [section.id, section.type, onEvent]);
2752
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: sectionRef, "data-section-id": section.id, "data-section-type": section.type, children: [
2753
+ el,
2754
+ showCOA && data?.coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
2860
2755
  ] });
2861
2756
  }
2862
- function toEmbedUrl(url) {
2863
- const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
2864
- if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
2865
- const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
2866
- if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
2867
- return url;
2868
- }
2869
2757
  function QRLandingPage({
2870
2758
  code,
2871
2759
  gatewayUrl = "https://whale-gateway.fly.dev",
@@ -3164,6 +3052,12 @@ function DefaultError({ message }) {
3164
3052
  /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#888" }, children: message || "Please try scanning again." })
3165
3053
  ] }) });
3166
3054
  }
3055
+ function getInlinedData() {
3056
+ if (typeof window !== "undefined" && window.__LANDING_DATA__) {
3057
+ return window.__LANDING_DATA__;
3058
+ }
3059
+ return null;
3060
+ }
3167
3061
  function LandingPage({
3168
3062
  slug,
3169
3063
  gatewayUrl = "https://whale-gateway.fly.dev",
@@ -3174,47 +3068,43 @@ function LandingPage({
3174
3068
  analyticsContext,
3175
3069
  enableAnalytics = true
3176
3070
  }) {
3177
- const [state, setState] = react.useState("loading");
3178
- const [data, setData] = react.useState(null);
3071
+ const inlined = react.useRef(getInlinedData()).current;
3072
+ const [state, setState] = react.useState(inlined ? "ready" : "loading");
3073
+ const [data, setData] = react.useState(inlined);
3179
3074
  const [errorMsg, setErrorMsg] = react.useState("");
3180
3075
  react.useEffect(() => {
3181
3076
  if (!slug) return;
3182
- let cancelled = false;
3183
- if (typeof window !== "undefined" && window.__LANDING_DATA__) {
3184
- const json = window.__LANDING_DATA__;
3185
- setData(json);
3186
- setState("ready");
3187
- onDataLoaded?.(json);
3077
+ if (data) {
3078
+ onDataLoaded?.(data);
3188
3079
  return;
3189
3080
  }
3081
+ let cancelled = false;
3190
3082
  async function load() {
3191
3083
  try {
3192
3084
  const res = await fetch(`${gatewayUrl}/l/${encodeURIComponent(slug)}`);
3193
- if (!cancelled) {
3194
- if (res.status === 404) {
3195
- setState("not_found");
3196
- return;
3197
- }
3198
- if (res.status === 410) {
3199
- setState("expired");
3200
- return;
3201
- }
3202
- if (!res.ok) {
3203
- const body = await res.json().catch(() => ({}));
3204
- throw new Error(body?.error?.message ?? `Failed to load: ${res.status}`);
3205
- }
3206
- const json = await res.json();
3207
- setData(json);
3208
- setState("ready");
3209
- onDataLoaded?.(json);
3085
+ if (cancelled) return;
3086
+ if (res.status === 404) {
3087
+ setState("not_found");
3088
+ return;
3210
3089
  }
3211
- } catch (err) {
3212
- if (!cancelled) {
3213
- const e = err instanceof Error ? err : new Error(String(err));
3214
- setErrorMsg(e.message);
3215
- setState("error");
3216
- onError?.(e);
3090
+ if (res.status === 410) {
3091
+ setState("expired");
3092
+ return;
3093
+ }
3094
+ if (!res.ok) {
3095
+ const body = await res.json().catch(() => ({}));
3096
+ throw new Error(body?.error?.message ?? `Failed to load: ${res.status}`);
3217
3097
  }
3098
+ const json = await res.json();
3099
+ setData(json);
3100
+ setState("ready");
3101
+ onDataLoaded?.(json);
3102
+ } catch (err) {
3103
+ if (cancelled) return;
3104
+ const e = err instanceof Error ? err : new Error(String(err));
3105
+ setErrorMsg(e.message);
3106
+ setState("error");
3107
+ onError?.(e);
3218
3108
  }
3219
3109
  }
3220
3110
  load();
@@ -3222,12 +3112,22 @@ function LandingPage({
3222
3112
  cancelled = true;
3223
3113
  };
3224
3114
  }, [slug, gatewayUrl]);
3225
- if (state === "loading") return /* @__PURE__ */ jsxRuntime.jsx(DefaultLoading2, {});
3226
- if (state === "not_found") return /* @__PURE__ */ jsxRuntime.jsx(DefaultNotFound2, {});
3227
- if (state === "expired") return /* @__PURE__ */ jsxRuntime.jsx(DefaultExpired2, {});
3228
- if (state === "error") return /* @__PURE__ */ jsxRuntime.jsx(DefaultError2, { message: errorMsg });
3115
+ if (state === "loading") return /* @__PURE__ */ jsxRuntime.jsx(StateScreen, { title: "", loading: true });
3116
+ if (state === "not_found") return /* @__PURE__ */ jsxRuntime.jsx(StateScreen, { title: "Page Not Found", subtitle: "This page does not exist or has been removed." });
3117
+ if (state === "expired") return /* @__PURE__ */ jsxRuntime.jsx(StateScreen, { title: "Page Expired", subtitle: "This page is no longer active." });
3118
+ if (state === "error") return /* @__PURE__ */ jsxRuntime.jsx(StateScreen, { title: "Something Went Wrong", subtitle: errorMsg || "Please try again later." });
3229
3119
  if (!data) return null;
3230
- return /* @__PURE__ */ jsxRuntime.jsx(PageLayout, { data, gatewayUrl, renderSection, onEvent, analyticsContext, enableAnalytics });
3120
+ return /* @__PURE__ */ jsxRuntime.jsx(
3121
+ PageLayout,
3122
+ {
3123
+ data,
3124
+ gatewayUrl,
3125
+ renderSection,
3126
+ onEvent,
3127
+ analyticsContext,
3128
+ enableAnalytics
3129
+ }
3130
+ );
3231
3131
  }
3232
3132
  function isSectionVisible(section, urlParams) {
3233
3133
  const vis = section.config?.visibility;
@@ -3250,8 +3150,8 @@ function PageLayout({
3250
3150
  const trackerRef = react.useRef(null);
3251
3151
  react.useEffect(() => {
3252
3152
  if (!enableAnalytics || typeof window === "undefined") return;
3253
- const analyticsConfig = window.__LANDING_ANALYTICS__;
3254
- if (!analyticsConfig?.slug) return;
3153
+ const config = window.__LANDING_ANALYTICS__;
3154
+ if (!config?.slug) return;
3255
3155
  let visitorId = localStorage.getItem("wt_vid") || "";
3256
3156
  if (!visitorId) {
3257
3157
  visitorId = crypto.randomUUID();
@@ -3263,8 +3163,8 @@ function PageLayout({
3263
3163
  sessionStorage.setItem("wt_sid", sessionId);
3264
3164
  }
3265
3165
  import('../tracker-WYKTEQ4F.cjs').then(({ BehavioralTracker: BehavioralTracker2 }) => {
3266
- const gwUrl = analyticsConfig.gatewayUrl || gatewayUrl;
3267
- const slug = analyticsConfig.slug;
3166
+ const gwUrl = config.gatewayUrl || gatewayUrl;
3167
+ const slug = config.slug;
3268
3168
  const utmParams = new URLSearchParams(window.location.search);
3269
3169
  const tracker = new BehavioralTracker2({
3270
3170
  sessionId,
@@ -3275,65 +3175,44 @@ function PageLayout({
3275
3175
  event_data: e.data,
3276
3176
  session_id: batch.session_id,
3277
3177
  visitor_id: batch.visitor_id,
3278
- campaign_id: analyticsConfig.campaignId || utmParams.get("utm_campaign_id") || void 0,
3178
+ campaign_id: config.campaignId || utmParams.get("utm_campaign_id") || void 0,
3279
3179
  utm_source: utmParams.get("utm_source") || void 0,
3280
3180
  utm_medium: utmParams.get("utm_medium") || void 0,
3281
3181
  utm_campaign: utmParams.get("utm_campaign") || void 0
3282
3182
  }));
3283
- const body = JSON.stringify({ events });
3284
- if (typeof navigator !== "undefined" && navigator.sendBeacon) {
3285
- navigator.sendBeacon(
3286
- `${gwUrl}/l/${encodeURIComponent(slug)}/events`,
3287
- new Blob([body], { type: "application/json" })
3288
- );
3289
- } else {
3290
- await fetch(`${gwUrl}/l/${encodeURIComponent(slug)}/events`, {
3291
- method: "POST",
3292
- headers: { "Content-Type": "application/json" },
3293
- body,
3294
- keepalive: true
3295
- });
3296
- }
3183
+ sendEvents(gwUrl, slug, events);
3297
3184
  }
3298
3185
  });
3299
3186
  tracker.setPageContext(window.location.href, window.location.pathname);
3300
3187
  tracker.start();
3301
3188
  trackerRef.current = tracker;
3302
- const pageViewBody = JSON.stringify({
3303
- events: [{
3304
- event_type: "page_view",
3305
- event_data: { referrer: document.referrer, url: window.location.href },
3306
- session_id: sessionId,
3307
- visitor_id: visitorId,
3308
- campaign_id: analyticsConfig.campaignId || void 0,
3309
- utm_source: utmParams.get("utm_source") || void 0,
3310
- utm_medium: utmParams.get("utm_medium") || void 0,
3311
- utm_campaign: utmParams.get("utm_campaign") || void 0
3312
- }]
3313
- });
3314
- if (navigator.sendBeacon) {
3315
- navigator.sendBeacon(
3316
- `${gwUrl}/l/${encodeURIComponent(slug)}/events`,
3317
- new Blob([pageViewBody], { type: "application/json" })
3318
- );
3319
- } else {
3320
- fetch(`${gwUrl}/l/${encodeURIComponent(slug)}/events`, {
3321
- method: "POST",
3322
- headers: { "Content-Type": "application/json" },
3323
- body: pageViewBody,
3324
- keepalive: true
3325
- }).catch(() => {
3326
- });
3327
- }
3189
+ sendEvents(gwUrl, slug, [{
3190
+ event_type: "page_view",
3191
+ event_data: { referrer: document.referrer, url: window.location.href },
3192
+ session_id: sessionId,
3193
+ visitor_id: visitorId,
3194
+ campaign_id: config.campaignId || void 0
3195
+ }]);
3328
3196
  }).catch(() => {
3329
3197
  });
3330
3198
  return () => {
3331
- if (trackerRef.current) {
3332
- trackerRef.current.stop();
3333
- trackerRef.current = null;
3334
- }
3199
+ trackerRef.current?.stop();
3200
+ trackerRef.current = null;
3335
3201
  };
3336
3202
  }, [enableAnalytics, gatewayUrl]);
3203
+ const handleEvent = react.useCallback((event, eventData) => {
3204
+ onEvent?.(event, eventData);
3205
+ if (!enableAnalytics || typeof window === "undefined") return;
3206
+ const config = window.__LANDING_ANALYTICS__;
3207
+ if (!config?.slug) return;
3208
+ sendEvents(config.gatewayUrl || gatewayUrl, config.slug, [{
3209
+ event_type: event,
3210
+ event_data: eventData,
3211
+ session_id: sessionStorage.getItem("wt_sid") || void 0,
3212
+ visitor_id: localStorage.getItem("wt_vid") || void 0,
3213
+ campaign_id: config.campaignId || void 0
3214
+ }]);
3215
+ }, [onEvent, enableAnalytics, gatewayUrl]);
3337
3216
  const theme = {
3338
3217
  bg: lp.background_color || store?.theme?.background || "#050505",
3339
3218
  fg: lp.text_color || store?.theme?.foreground || "#fafafa",
@@ -3352,11 +3231,9 @@ function PageLayout({
3352
3231
  lp.custom_css && /* @__PURE__ */ jsxRuntime.jsx("style", { children: lp.custom_css }),
3353
3232
  logoUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: store?.name || "Store", style: { height: 40, objectFit: "contain" } }) }),
3354
3233
  sorted.map((section) => {
3355
- const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data: sectionData, theme, onEvent }, section.id);
3356
- if (renderSection) {
3357
- return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
3358
- }
3359
- return /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data: sectionData, theme, onEvent }, section.id);
3234
+ const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data: sectionData, theme, onEvent: handleEvent }, section.id);
3235
+ if (renderSection) return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
3236
+ return /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data: sectionData, theme, onEvent: handleEvent }, section.id);
3360
3237
  }),
3361
3238
  store?.name && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "2rem 1.5rem", borderTop: `1px solid ${theme.surface}`, textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.75rem", color: theme.muted, margin: 0 }, children: [
3362
3239
  "Powered by ",
@@ -3364,7 +3241,17 @@ function PageLayout({
3364
3241
  ] }) })
3365
3242
  ] });
3366
3243
  }
3367
- var containerStyle2 = {
3244
+ function sendEvents(gwUrl, slug, events) {
3245
+ const body = JSON.stringify({ events });
3246
+ const url = `${gwUrl}/l/${encodeURIComponent(slug)}/events`;
3247
+ if (typeof navigator !== "undefined" && navigator.sendBeacon) {
3248
+ navigator.sendBeacon(url, new Blob([body], { type: "application/json" }));
3249
+ } else {
3250
+ fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body, keepalive: true }).catch(() => {
3251
+ });
3252
+ }
3253
+ }
3254
+ var screenStyle = {
3368
3255
  minHeight: "100dvh",
3369
3256
  display: "flex",
3370
3257
  justifyContent: "center",
@@ -3375,28 +3262,14 @@ var containerStyle2 = {
3375
3262
  textAlign: "center",
3376
3263
  padding: "2rem"
3377
3264
  };
3378
- function DefaultLoading2() {
3379
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3380
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 32, height: 32, border: "2px solid #333", borderTopColor: "#fafafa", borderRadius: "50%", animation: "spin 0.8s linear infinite", margin: "0 auto 1rem" } }),
3381
- /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg) } }` })
3382
- ] }) });
3383
- }
3384
- function DefaultNotFound2() {
3385
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3386
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Page Not Found" }),
3387
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#888" }, children: "This page does not exist or has been removed." })
3388
- ] }) });
3389
- }
3390
- function DefaultExpired2() {
3391
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3392
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Page Expired" }),
3393
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#888" }, children: "This page is no longer active." })
3394
- ] }) });
3395
- }
3396
- function DefaultError2({ message }) {
3397
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: containerStyle2, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3398
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: "Something Went Wrong" }),
3399
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#888" }, children: message || "Please try again later." })
3265
+ function StateScreen({ title, subtitle, loading }) {
3266
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: screenStyle, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3267
+ loading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3268
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 32, height: 32, border: "2px solid #333", borderTopColor: "#fafafa", borderRadius: "50%", animation: "spin 0.8s linear infinite", margin: "0 auto 1rem" } }),
3269
+ /* @__PURE__ */ jsxRuntime.jsx("style", { children: `@keyframes spin { to { transform: rotate(360deg) } }` })
3270
+ ] }),
3271
+ title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "1.5rem", marginBottom: "0.5rem" }, children: title }),
3272
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "#888" }, children: subtitle })
3400
3273
  ] }) });
3401
3274
  }
3402
3275