@neowhale/storefront 0.2.11 → 0.2.12

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.
@@ -1208,12 +1208,382 @@ function useCoupons() {
1208
1208
  }, []);
1209
1209
  return { validation, loading, error, validate, apply, remove, clear };
1210
1210
  }
1211
+ function SectionRenderer({
1212
+ section,
1213
+ data,
1214
+ theme
1215
+ }) {
1216
+ const [showCOA, setShowCOA] = react.useState(false);
1217
+ const el = (() => {
1218
+ switch (section.type) {
1219
+ case "hero":
1220
+ return /* @__PURE__ */ jsxRuntime.jsx(HeroSection, { section, theme });
1221
+ case "text":
1222
+ return /* @__PURE__ */ jsxRuntime.jsx(TextSection, { section, theme });
1223
+ case "image":
1224
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageSection, { section, theme });
1225
+ case "video":
1226
+ return /* @__PURE__ */ jsxRuntime.jsx(VideoSection, { section, theme });
1227
+ case "gallery":
1228
+ return /* @__PURE__ */ jsxRuntime.jsx(GallerySection, { section, theme });
1229
+ case "cta":
1230
+ return /* @__PURE__ */ jsxRuntime.jsx(CTASection, { section, theme });
1231
+ case "stats":
1232
+ return /* @__PURE__ */ jsxRuntime.jsx(StatsSection, { section, theme });
1233
+ case "product_card":
1234
+ return /* @__PURE__ */ jsxRuntime.jsx(ProductCardSection, { section, data, theme });
1235
+ case "coa_viewer":
1236
+ return /* @__PURE__ */ jsxRuntime.jsx(COAViewerSection, { section, data, theme, onShowCOA: () => setShowCOA(true) });
1237
+ case "social_links":
1238
+ return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
1239
+ case "divider":
1240
+ return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
1241
+ default:
1242
+ return null;
1243
+ }
1244
+ })();
1245
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1246
+ el,
1247
+ showCOA && data?.coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa: data.coa, theme, onClose: () => setShowCOA(false) })
1248
+ ] });
1249
+ }
1250
+ function HeroSection({ section, theme }) {
1251
+ const { title, subtitle, background_image, cta_text, cta_url } = section.content;
1252
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1253
+ "div",
1254
+ {
1255
+ style: {
1256
+ position: "relative",
1257
+ minHeight: "60vh",
1258
+ display: "flex",
1259
+ flexDirection: "column",
1260
+ justifyContent: "center",
1261
+ alignItems: "center",
1262
+ textAlign: "center",
1263
+ padding: "3rem 1.5rem",
1264
+ backgroundImage: background_image ? `url(${background_image})` : void 0,
1265
+ backgroundSize: "cover",
1266
+ backgroundPosition: "center"
1267
+ },
1268
+ children: [
1269
+ background_image && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
1270
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
1271
+ title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: {
1272
+ fontSize: "clamp(2rem, 8vw, 3rem)",
1273
+ fontWeight: 300,
1274
+ fontFamily: theme.fontDisplay || "inherit",
1275
+ margin: "0 0 1rem",
1276
+ lineHeight: 1.15,
1277
+ letterSpacing: "-0.02em",
1278
+ color: theme.fg
1279
+ }, children: title }),
1280
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
1281
+ fontSize: "0.85rem",
1282
+ color: theme.accent,
1283
+ margin: "0 0 2rem",
1284
+ lineHeight: 1.6,
1285
+ textTransform: "uppercase",
1286
+ letterSpacing: "0.15em"
1287
+ }, children: subtitle }),
1288
+ cta_text && cta_url && /* @__PURE__ */ jsxRuntime.jsx(
1289
+ "a",
1290
+ {
1291
+ href: cta_url,
1292
+ style: {
1293
+ display: "inline-block",
1294
+ padding: "0.875rem 2rem",
1295
+ background: theme.fg,
1296
+ color: theme.bg,
1297
+ textDecoration: "none",
1298
+ fontSize: "0.85rem",
1299
+ fontWeight: 500,
1300
+ letterSpacing: "0.08em",
1301
+ textTransform: "uppercase"
1302
+ },
1303
+ children: cta_text
1304
+ }
1305
+ )
1306
+ ] })
1307
+ ]
1308
+ }
1309
+ );
1310
+ }
1311
+ function TextSection({ section, theme }) {
1312
+ const { heading, body } = section.content;
1313
+ const align = section.config?.align || "left";
1314
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 640, margin: "0 auto", textAlign: align }, children: [
1315
+ heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
1316
+ fontSize: 11,
1317
+ fontWeight: 500,
1318
+ textTransform: "uppercase",
1319
+ letterSpacing: "0.25em",
1320
+ color: `${theme.fg}40`,
1321
+ margin: "0 0 0.75rem"
1322
+ }, children: heading }),
1323
+ body && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: `${theme.fg}99`, lineHeight: 1.7, fontSize: "0.9rem", fontWeight: 300, whiteSpace: "pre-wrap" }, children: body })
1324
+ ] });
1325
+ }
1326
+ function ImageSection({ section, theme }) {
1327
+ const { url, alt, caption } = section.content;
1328
+ const contained = section.config?.contained !== false;
1329
+ if (!url) return null;
1330
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: contained ? "1.5rem" : 0, maxWidth: contained ? 640 : void 0, margin: contained ? "0 auto" : void 0 }, children: [
1331
+ /* @__PURE__ */ jsxRuntime.jsx(
1332
+ "img",
1333
+ {
1334
+ src: url,
1335
+ alt: alt || "",
1336
+ style: { width: "100%", display: "block", objectFit: "cover" }
1337
+ }
1338
+ ),
1339
+ caption && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, textAlign: "center", marginTop: "0.75rem" }, children: caption })
1340
+ ] });
1341
+ }
1342
+ function VideoSection({ section, theme }) {
1343
+ const { url, poster } = section.content;
1344
+ if (!url) return null;
1345
+ const isEmbed = url.includes("youtube") || url.includes("youtu.be") || url.includes("vimeo");
1346
+ 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(
1347
+ "iframe",
1348
+ {
1349
+ src: toEmbedUrl(url),
1350
+ style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" },
1351
+ allow: "autoplay; fullscreen",
1352
+ title: "Video"
1353
+ }
1354
+ ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1355
+ "video",
1356
+ {
1357
+ src: url,
1358
+ poster,
1359
+ controls: true,
1360
+ style: { width: "100%", display: "block", background: theme.surface }
1361
+ }
1362
+ ) });
1363
+ }
1364
+ function GallerySection({ section, theme }) {
1365
+ const { images } = section.content;
1366
+ const columns = section.config?.columns || 3;
1367
+ if (!images || images.length === 0) return null;
1368
+ 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(
1369
+ "img",
1370
+ {
1371
+ src: img.url,
1372
+ alt: img.alt || "",
1373
+ style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
1374
+ }
1375
+ ) }, i)) }) });
1376
+ }
1377
+ function CTASection({ section, theme }) {
1378
+ const { buttons } = section.content;
1379
+ if (!buttons || buttons.length === 0) return null;
1380
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: buttons.map((btn, i) => {
1381
+ const isPrimary = btn.style !== "outline";
1382
+ return /* @__PURE__ */ jsxRuntime.jsx(
1383
+ "a",
1384
+ {
1385
+ href: btn.url,
1386
+ style: {
1387
+ display: "block",
1388
+ width: "100%",
1389
+ padding: "0.875rem",
1390
+ background: isPrimary ? theme.fg : "transparent",
1391
+ color: isPrimary ? theme.bg : theme.fg,
1392
+ border: isPrimary ? "none" : `1px solid ${theme.fg}20`,
1393
+ fontSize: "0.85rem",
1394
+ fontWeight: 500,
1395
+ textAlign: "center",
1396
+ textDecoration: "none",
1397
+ boxSizing: "border-box",
1398
+ letterSpacing: "0.08em",
1399
+ textTransform: "uppercase"
1400
+ },
1401
+ children: btn.text
1402
+ },
1403
+ i
1404
+ );
1405
+ }) });
1406
+ }
1407
+ function StatsSection({ section, theme }) {
1408
+ const { stats } = section.content;
1409
+ const layout = section.config?.layout;
1410
+ if (!stats || stats.length === 0) return null;
1411
+ if (layout === "list") {
1412
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1413
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1414
+ display: "flex",
1415
+ justifyContent: "space-between",
1416
+ alignItems: "baseline",
1417
+ padding: "0.625rem 0"
1418
+ }, children: [
1419
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1420
+ fontSize: 12,
1421
+ textTransform: "uppercase",
1422
+ letterSpacing: "0.15em",
1423
+ color: `${theme.fg}66`
1424
+ }, children: stat.label }),
1425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: 14, fontWeight: 300, color: `${theme.fg}CC` }, children: stat.value })
1426
+ ] }),
1427
+ i < stats.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("hr", { style: { border: "none", borderTop: `1px solid ${theme.fg}0A`, margin: 0 } })
1428
+ ] }, i)) });
1429
+ }
1430
+ const columns = Math.min(stats.length, 4);
1431
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 640, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1432
+ display: "grid",
1433
+ gridTemplateColumns: `repeat(${columns}, 1fr)`,
1434
+ border: `1px solid ${theme.fg}0F`
1435
+ }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1436
+ padding: "1.25rem 0.5rem",
1437
+ textAlign: "center",
1438
+ borderRight: i < stats.length - 1 ? `1px solid ${theme.fg}0F` : void 0
1439
+ }, children: [
1440
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1441
+ fontFamily: theme.fontDisplay || "inherit",
1442
+ fontSize: "clamp(1.5rem, 5vw, 2rem)",
1443
+ fontWeight: 300,
1444
+ lineHeight: 1,
1445
+ color: theme.fg
1446
+ }, children: stat.value }),
1447
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1448
+ fontSize: 11,
1449
+ fontWeight: 500,
1450
+ textTransform: "uppercase",
1451
+ letterSpacing: "0.25em",
1452
+ color: theme.accent,
1453
+ marginTop: "0.5rem"
1454
+ }, children: stat.label })
1455
+ ] }, i)) }) });
1456
+ }
1457
+ function ProductCardSection({ section, data, theme }) {
1458
+ const product = data?.product;
1459
+ const c = section.content;
1460
+ const name = c.name || product?.name || "";
1461
+ const description = c.description || product?.description || "";
1462
+ const imageUrl = c.image_url || product?.featured_image || null;
1463
+ const url = c.url || null;
1464
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, overflow: "hidden" }, children: [
1465
+ imageUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", aspectRatio: "1", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: imageUrl, alt: name, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }),
1466
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "1.25rem" }, children: [
1467
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 0.5rem", color: theme.fg }, children: name }),
1468
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9rem", color: theme.muted, margin: "0 0 1rem", lineHeight: 1.5 }, children: description }),
1469
+ url && /* @__PURE__ */ jsxRuntime.jsx(
1470
+ "a",
1471
+ {
1472
+ href: url,
1473
+ style: {
1474
+ display: "block",
1475
+ width: "100%",
1476
+ padding: "0.75rem",
1477
+ background: theme.fg,
1478
+ color: theme.bg,
1479
+ textAlign: "center",
1480
+ textDecoration: "none",
1481
+ fontSize: "0.85rem",
1482
+ fontWeight: 500,
1483
+ boxSizing: "border-box",
1484
+ letterSpacing: "0.08em",
1485
+ textTransform: "uppercase"
1486
+ },
1487
+ children: "View Product"
1488
+ }
1489
+ )
1490
+ ] })
1491
+ ] }) });
1492
+ }
1493
+ function COAViewerSection({
1494
+ section,
1495
+ data,
1496
+ theme,
1497
+ onShowCOA
1498
+ }) {
1499
+ const coa = data?.coa;
1500
+ const c = section.content;
1501
+ if (!coa) return null;
1502
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1503
+ "button",
1504
+ {
1505
+ onClick: onShowCOA,
1506
+ style: {
1507
+ width: "100%",
1508
+ padding: "0.875rem",
1509
+ background: theme.accent,
1510
+ color: theme.bg,
1511
+ border: "none",
1512
+ fontSize: "0.85rem",
1513
+ fontWeight: 500,
1514
+ cursor: "pointer",
1515
+ letterSpacing: "0.08em",
1516
+ textTransform: "uppercase"
1517
+ },
1518
+ children: c.button_text || "View Lab Results"
1519
+ }
1520
+ ) });
1521
+ }
1522
+ function SocialLinksSection({ section, theme }) {
1523
+ const { links } = section.content;
1524
+ if (!links || links.length === 0) return null;
1525
+ 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(
1526
+ "a",
1527
+ {
1528
+ href: link.url,
1529
+ target: "_blank",
1530
+ rel: "noopener noreferrer",
1531
+ style: {
1532
+ color: theme.muted,
1533
+ textDecoration: "none",
1534
+ fontSize: "0.85rem",
1535
+ fontWeight: 500,
1536
+ textTransform: "capitalize",
1537
+ letterSpacing: "0.03em"
1538
+ },
1539
+ children: link.platform
1540
+ },
1541
+ i
1542
+ )) });
1543
+ }
1544
+ function DividerSection({ theme }) {
1545
+ 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 } }) });
1546
+ }
1547
+ function COAModal({ coa, theme, onClose }) {
1548
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column" }, children: [
1549
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1550
+ display: "flex",
1551
+ justifyContent: "space-between",
1552
+ alignItems: "center",
1553
+ padding: "0.75rem 1rem",
1554
+ borderBottom: `1px solid ${theme.fg}10`
1555
+ }, children: [
1556
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fff", fontWeight: 500, fontSize: "0.85rem" }, children: coa.document_name || "Lab Results" }),
1557
+ /* @__PURE__ */ jsxRuntime.jsx(
1558
+ "button",
1559
+ {
1560
+ onClick: onClose,
1561
+ style: {
1562
+ background: `${theme.fg}10`,
1563
+ border: "none",
1564
+ color: "#fff",
1565
+ fontSize: "0.85rem",
1566
+ cursor: "pointer",
1567
+ padding: "0.375rem 0.75rem"
1568
+ },
1569
+ children: "Close"
1570
+ }
1571
+ )
1572
+ ] }),
1573
+ /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
1574
+ ] });
1575
+ }
1576
+ function toEmbedUrl(url) {
1577
+ const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
1578
+ if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
1579
+ const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
1580
+ if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
1581
+ return url;
1582
+ }
1211
1583
  function QRLandingPage({
1212
1584
  code,
1213
1585
  gatewayUrl = "https://whale-gateway.fly.dev",
1214
- renderProduct,
1215
- renderCOA,
1216
- renderPage,
1586
+ renderSection,
1217
1587
  onDataLoaded,
1218
1588
  onError
1219
1589
  }) {
@@ -1263,387 +1633,140 @@ function QRLandingPage({
1263
1633
  if (state === "expired") return /* @__PURE__ */ jsxRuntime.jsx(DefaultExpired, {});
1264
1634
  if (state === "error") return /* @__PURE__ */ jsxRuntime.jsx(DefaultError, { message: errorMsg });
1265
1635
  if (!data) return null;
1266
- if (renderPage) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderPage(data) });
1267
- return /* @__PURE__ */ jsxRuntime.jsx(ProductLanding, { data, renderProduct, renderCOA });
1636
+ const lp = data.landing_page;
1637
+ const sections = lp?.sections ?? buildDefaultSections(data);
1638
+ const theme = extractTheme(data, lp);
1639
+ const fontFamily = lp?.font_family || data.store?.theme?.fontDisplay || "system-ui, -apple-system, sans-serif";
1640
+ const logoUrl = data.qr_code.logo_url || data.store?.logo_url;
1641
+ const storeName = data.store?.name;
1642
+ const sorted = [...sections].sort((a, b) => a.order - b.order);
1643
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
1644
+ lp?.custom_css && /* @__PURE__ */ jsxRuntime.jsx("style", { children: lp.custom_css }),
1645
+ logoUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: storeName || "Store", style: { height: 40, objectFit: "contain" } }) }),
1646
+ sorted.map((section) => {
1647
+ const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1648
+ if (renderSection) {
1649
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
1650
+ }
1651
+ return /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1652
+ }),
1653
+ storeName && /* @__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: [
1654
+ storeName,
1655
+ data.store?.tagline ? ` \u2014 ${data.store.tagline}` : ""
1656
+ ] }) })
1657
+ ] });
1268
1658
  }
1269
- function extractTheme(data) {
1270
- const lp = data.qr_code.landing_page;
1659
+ function extractTheme(data, lp) {
1271
1660
  const t = data.store?.theme;
1661
+ const qrLp = data.qr_code.landing_page;
1272
1662
  return {
1273
- bg: lp.background_color || t?.background || "#050505",
1274
- fg: lp.text_color || t?.foreground || "#fafafa",
1275
- accent: t?.accent || "#E8E2D9",
1276
- accentDark: t?.accentDark || "#C8BFB2",
1277
- surface: t?.surface || "#0C0C0C",
1278
- surfaceLight: t?.surfaceLight || "#141414",
1279
- muted: t?.muted || "#8A8A8A",
1280
- border: t?.border || "#1C1C1C",
1663
+ bg: lp?.background_color || qrLp.background_color || t?.background || "#050505",
1664
+ fg: lp?.text_color || qrLp.text_color || t?.foreground || "#fafafa",
1665
+ accent: lp?.accent_color || t?.accent || "#E8E2D9",
1666
+ surface: t?.surface || "#111",
1667
+ muted: t?.muted || "#888",
1281
1668
  fontDisplay: t?.fontDisplay || "system-ui, -apple-system, sans-serif",
1282
- fontBody: t?.fontBody || "system-ui, -apple-system, sans-serif",
1283
- radius: t?.radius || "0px"
1669
+ fontBody: t?.fontBody || "system-ui, -apple-system, sans-serif"
1284
1670
  };
1285
1671
  }
1286
- function ProductLanding({
1287
- data,
1288
- renderProduct,
1289
- renderCOA
1290
- }) {
1291
- const { qr_code: qr, store, product, coa } = data;
1292
- const lp = qr.landing_page;
1293
- const theme = extractTheme(data);
1294
- const [showCOA, setShowCOA] = react.useState(false);
1295
- const logoUrl = qr.logo_url || store?.logo_url;
1296
- const productImage = lp.image_url || product?.featured_image || null;
1297
- const productName = lp.title || product?.name || qr.name;
1298
- const description = lp.description || product?.description || "";
1299
- const ctaUrl = lp.cta_url || qr.destination_url;
1300
- const categoryName = product?.category_name ?? null;
1672
+ function buildDefaultSections(data) {
1673
+ const { product, coa, qr_code: qr } = data;
1301
1674
  const cf = product?.custom_fields;
1302
- const thca = toNum(cf?.thca_percentage);
1303
- const thc = toNum(cf?.d9_percentage);
1304
- const cbd = toNum(cf?.cbd_total);
1675
+ const sections = [];
1676
+ let order = 0;
1677
+ const productName = qr.landing_page.title || product?.name || qr.name;
1678
+ const productImage = qr.landing_page.image_url || product?.featured_image || null;
1679
+ const description = qr.landing_page.description || product?.description || "";
1680
+ const ctaUrl = qr.landing_page.cta_url || qr.destination_url;
1681
+ const categoryName = product?.category_name ?? null;
1305
1682
  const strainType = toStr(cf?.strain_type);
1306
- const batchNumber = toStr(cf?.batch_number);
1307
- const dateTested = toStr(cf?.date_tested);
1308
- const terpenes = toStr(cf?.terpenes);
1309
- const effects = toStr(cf?.effects);
1310
- const genetics = toStr(cf?.genetics);
1311
- const tagline = toStr(cf?.tagline);
1312
- const handleCOAClick = react.useCallback(() => {
1313
- if (coa) setShowCOA(true);
1314
- }, [coa]);
1315
- const labelStyle = {
1316
- fontSize: 11,
1317
- fontWeight: 500,
1318
- textTransform: "uppercase",
1319
- letterSpacing: "0.25em",
1320
- color: `${theme.fg}40`
1321
- // 40 = 25% opacity
1322
- };
1323
- const dividerStyle = {
1324
- border: "none",
1325
- borderTop: `1px solid ${theme.fg}0A`,
1326
- // 0A = 4% opacity
1327
- margin: 0
1328
- };
1329
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1330
- minHeight: "100dvh",
1331
- background: theme.bg,
1332
- color: theme.fg,
1333
- fontFamily: theme.fontBody
1334
- }, children: [
1335
- /* @__PURE__ */ jsxRuntime.jsxs("nav", { style: {
1336
- padding: "1.25rem 1.5rem",
1337
- display: "flex",
1338
- alignItems: "center",
1339
- justifyContent: "space-between",
1340
- maxWidth: 1280,
1341
- margin: "0 auto"
1342
- }, children: [
1343
- logoUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: store?.name || "", style: { height: 28, objectFit: "contain" } }) : store?.name ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: theme.fontDisplay, fontWeight: 600, fontSize: "1rem", letterSpacing: "-0.02em" }, children: store.name }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
1344
- /* @__PURE__ */ jsxRuntime.jsx(
1345
- "a",
1346
- {
1347
- href: ctaUrl,
1348
- style: {
1349
- fontSize: 11,
1350
- fontWeight: 500,
1351
- textTransform: "uppercase",
1352
- letterSpacing: "0.15em",
1353
- color: theme.fg,
1354
- textDecoration: "none",
1355
- padding: "0.5rem 1.25rem",
1356
- border: `1px solid ${theme.fg}10`,
1357
- transition: "border-color 0.3s"
1358
- },
1359
- children: "Shop"
1360
- }
1361
- )
1362
- ] }),
1363
- /* @__PURE__ */ jsxRuntime.jsxs("main", { style: { maxWidth: 560, margin: "0 auto", padding: "0 1.5rem 3rem" }, children: [
1364
- productImage && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1365
- width: "100%",
1366
- aspectRatio: "1",
1367
- overflow: "hidden",
1368
- background: theme.surfaceLight,
1369
- display: "flex",
1370
- alignItems: "center",
1371
- justifyContent: "center",
1372
- padding: "2rem"
1373
- }, children: /* @__PURE__ */ jsxRuntime.jsx(
1374
- "img",
1375
- {
1376
- src: productImage,
1377
- alt: productName,
1378
- style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }
1379
- }
1380
- ) }),
1381
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.5rem", display: "flex", gap: "0.75rem", alignItems: "center" }, children: [
1382
- categoryName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1383
- fontSize: 11,
1384
- fontWeight: 500,
1385
- color: theme.accent,
1386
- textTransform: "uppercase",
1387
- letterSpacing: "0.15em"
1388
- }, children: categoryName }),
1389
- strainType && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1390
- fontSize: 10,
1391
- fontWeight: 600,
1392
- textTransform: "uppercase",
1393
- letterSpacing: "0.08em",
1394
- padding: "0.2rem 0.5rem",
1395
- background: strainBadgeColor(strainType),
1396
- color: "#fff"
1397
- }, children: strainType })
1398
- ] }),
1399
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: {
1400
- fontFamily: theme.fontDisplay,
1401
- fontSize: "clamp(2rem, 8vw, 3rem)",
1402
- fontWeight: 300,
1403
- margin: "0.5rem 0 0",
1404
- lineHeight: 1.1,
1405
- letterSpacing: "-0.02em"
1406
- }, children: productName }),
1407
- tagline && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
1408
- fontFamily: theme.fontDisplay,
1409
- fontSize: "1.1rem",
1410
- fontWeight: 300,
1411
- color: `${theme.fg}80`,
1412
- margin: "0.5rem 0 0"
1413
- }, children: tagline }),
1414
- renderProduct ? renderProduct(data) : null,
1415
- (thca != null || thc != null || cbd != null) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1416
- marginTop: "1.75rem",
1417
- border: `1px solid ${theme.fg}0F`,
1418
- display: "grid",
1419
- gridTemplateColumns: `repeat(${[thca, thc, cbd].filter((v) => v != null).length}, 1fr)`
1420
- }, children: [
1421
- thca != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "THCa", value: thca, theme, showBorder: thc != null || cbd != null }),
1422
- thc != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "\u03949 THC", value: thc, theme, showBorder: cbd != null }),
1423
- cbd != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "CBD", value: cbd, theme, showBorder: false })
1424
- ] }),
1425
- (genetics || terpenes || effects || batchNumber || dateTested) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1426
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "Details" }),
1427
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1428
- genetics && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1429
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Genetics", value: genetics, theme }),
1430
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1431
- ] }),
1432
- terpenes && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1433
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Terpenes", value: terpenes, theme }),
1434
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1435
- ] }),
1436
- effects && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1437
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Effects", value: effects, theme }),
1438
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1439
- ] }),
1440
- batchNumber && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1441
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Batch", value: batchNumber, theme }),
1442
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1443
- ] }),
1444
- dateTested && /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Tested", value: formatDate(dateTested), theme })
1445
- ] })
1446
- ] }),
1447
- description && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1448
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "About" }),
1449
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
1450
- fontSize: "0.9rem",
1451
- fontWeight: 300,
1452
- color: `${theme.fg}99`,
1453
- lineHeight: 1.7,
1454
- margin: 0
1455
- }, children: description })
1456
- ] }),
1457
- coa && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1458
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "Lab Results" }),
1459
- batchNumber && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 12, color: `${theme.fg}60`, margin: "0 0 0.5rem", letterSpacing: "0.05em" }, children: [
1460
- "Batch ",
1461
- batchNumber,
1462
- dateTested ? ` \xB7 ${formatDate(dateTested)}` : ""
1463
- ] }),
1464
- /* @__PURE__ */ jsxRuntime.jsxs(
1465
- "button",
1466
- {
1467
- onClick: handleCOAClick,
1468
- style: {
1469
- width: "100%",
1470
- position: "relative",
1471
- height: 180,
1472
- border: `1px solid ${theme.fg}0F`,
1473
- background: theme.surface,
1474
- cursor: "pointer",
1475
- overflow: "hidden",
1476
- padding: 0,
1477
- display: "block"
1478
- },
1479
- children: [
1480
- /* @__PURE__ */ jsxRuntime.jsx(
1481
- "iframe",
1482
- {
1483
- src: coa.url,
1484
- style: {
1485
- width: "200%",
1486
- height: "200%",
1487
- border: "none",
1488
- transform: "scale(0.5)",
1489
- transformOrigin: "top left",
1490
- pointerEvents: "none"
1491
- },
1492
- title: "COA Preview",
1493
- tabIndex: -1
1494
- }
1495
- ),
1496
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1497
- position: "absolute",
1498
- inset: 0,
1499
- background: `linear-gradient(to top, ${theme.bg}E6 0%, transparent 60%)`,
1500
- display: "flex",
1501
- alignItems: "flex-end",
1502
- justifyContent: "center",
1503
- paddingBottom: "1.25rem"
1504
- }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1505
- fontSize: 11,
1506
- fontWeight: 500,
1507
- textTransform: "uppercase",
1508
- letterSpacing: "0.2em",
1509
- color: theme.accent
1510
- }, children: "View Certificate of Analysis" }) })
1511
- ]
1512
- }
1513
- )
1514
- ] }),
1515
- renderCOA ? renderCOA(data) : null,
1516
- /* @__PURE__ */ jsxRuntime.jsx(
1517
- "a",
1518
- {
1519
- href: ctaUrl,
1520
- style: {
1521
- display: "block",
1522
- width: "100%",
1523
- marginTop: "2rem",
1524
- padding: "1rem",
1525
- background: theme.fg,
1526
- color: theme.bg,
1527
- fontFamily: theme.fontDisplay,
1528
- fontSize: "0.85rem",
1529
- fontWeight: 500,
1530
- textAlign: "center",
1531
- textDecoration: "none",
1532
- letterSpacing: "0.08em",
1533
- textTransform: "uppercase",
1534
- boxSizing: "border-box"
1535
- },
1536
- children: "Shop Online"
1537
- }
1538
- ),
1539
- /* @__PURE__ */ jsxRuntime.jsxs("footer", { style: {
1540
- marginTop: "3rem",
1541
- paddingTop: "1.5rem",
1542
- borderTop: `1px solid ${theme.fg}0A`,
1543
- textAlign: "center"
1544
- }, children: [
1545
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "center", gap: "2rem", marginBottom: "0.75rem" }, children: [
1546
- coa && /* @__PURE__ */ jsxRuntime.jsx(FooterLabel, { text: "Lab Verified" }),
1547
- /* @__PURE__ */ jsxRuntime.jsx(FooterLabel, { text: "Authentic Product" })
1548
- ] }),
1549
- store?.name && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 11, color: `${theme.fg}40`, margin: "0.5rem 0 0", letterSpacing: "0.1em" }, children: [
1550
- store.name,
1551
- store?.tagline ? ` \u2014 ${store.tagline}` : ""
1552
- ] })
1553
- ] })
1554
- ] }),
1555
- showCOA && coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa, theme, onClose: () => setShowCOA(false) })
1556
- ] });
1557
- }
1558
- function CannabinoidStat({ label, value, theme, showBorder }) {
1559
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1560
- padding: "1.25rem 0.5rem",
1561
- textAlign: "center",
1562
- borderRight: showBorder ? `1px solid ${theme.fg}0F` : void 0
1563
- }, children: [
1564
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1565
- fontFamily: theme.fontDisplay,
1566
- fontSize: "clamp(1.5rem, 5vw, 2rem)",
1567
- fontWeight: 300,
1568
- lineHeight: 1,
1569
- color: theme.fg
1570
- }, children: [
1571
- value.toFixed(value >= 1 ? 1 : 2),
1572
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.6em", marginLeft: 1 }, children: "%" })
1573
- ] }),
1574
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1575
- fontSize: 11,
1576
- fontWeight: 500,
1577
- textTransform: "uppercase",
1578
- letterSpacing: "0.25em",
1579
- color: theme.accent,
1580
- marginTop: "0.5rem"
1581
- }, children: label })
1582
- ] });
1583
- }
1584
- function DetailRow({ label, value, theme }) {
1585
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1586
- display: "flex",
1587
- justifyContent: "space-between",
1588
- alignItems: "baseline",
1589
- padding: "0.625rem 0"
1590
- }, children: [
1591
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1592
- fontSize: 12,
1593
- textTransform: "uppercase",
1594
- letterSpacing: "0.15em",
1595
- color: `${theme.fg}66`
1596
- }, children: label }),
1597
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1598
- fontSize: 14,
1599
- fontWeight: 300,
1600
- color: `${theme.fg}CC`
1601
- }, children: value })
1602
- ] });
1603
- }
1604
- function FooterLabel({ text }) {
1605
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1606
- fontSize: 10,
1607
- textTransform: "uppercase",
1608
- letterSpacing: "0.15em",
1609
- color: "rgba(255,255,255,0.25)"
1610
- }, children: text });
1611
- }
1612
- function COAModal({ coa, theme, onClose }) {
1613
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column" }, children: [
1614
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1615
- display: "flex",
1616
- justifyContent: "space-between",
1617
- alignItems: "center",
1618
- padding: "0.75rem 1rem",
1619
- borderBottom: `1px solid ${theme.fg}10`
1620
- }, children: [
1621
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1622
- color: "#fff",
1623
- fontFamily: theme.fontDisplay,
1624
- fontWeight: 500,
1625
- fontSize: "0.85rem",
1626
- letterSpacing: "-0.01em"
1627
- }, children: coa.document_name || "Lab Results" }),
1628
- /* @__PURE__ */ jsxRuntime.jsx(
1629
- "button",
1630
- {
1631
- onClick: onClose,
1632
- style: {
1633
- background: `${theme.fg}10`,
1634
- border: "none",
1635
- color: "#fff",
1636
- fontSize: "0.85rem",
1637
- cursor: "pointer",
1638
- padding: "0.375rem 0.75rem",
1639
- letterSpacing: "0.05em"
1640
- },
1641
- children: "Close"
1642
- }
1643
- )
1644
- ] }),
1645
- /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
1646
- ] });
1683
+ if (productImage) {
1684
+ sections.push({
1685
+ id: "auto-hero",
1686
+ type: "hero",
1687
+ order: order++,
1688
+ content: {
1689
+ title: productName,
1690
+ subtitle: [categoryName, strainType].filter(Boolean).join(" \xB7 "),
1691
+ background_image: productImage,
1692
+ cta_text: qr.landing_page.cta_text || null,
1693
+ cta_url: ctaUrl
1694
+ }
1695
+ });
1696
+ } else {
1697
+ sections.push({
1698
+ id: "auto-header",
1699
+ type: "text",
1700
+ order: order++,
1701
+ content: {
1702
+ heading: productName,
1703
+ body: [categoryName, strainType].filter(Boolean).join(" \xB7 ") || void 0
1704
+ },
1705
+ config: { align: "center" }
1706
+ });
1707
+ }
1708
+ const thca = toNum(cf?.thca_percentage);
1709
+ const thc = toNum(cf?.d9_percentage);
1710
+ const cbd = toNum(cf?.cbd_total);
1711
+ const stats = [];
1712
+ if (thca != null) stats.push({ label: "THCa", value: `${thca.toFixed(thca >= 1 ? 1 : 2)}%` });
1713
+ if (thc != null) stats.push({ label: "\u03949 THC", value: `${thc.toFixed(thc >= 1 ? 1 : 2)}%` });
1714
+ if (cbd != null) stats.push({ label: "CBD", value: `${cbd.toFixed(cbd >= 1 ? 1 : 2)}%` });
1715
+ if (stats.length > 0) {
1716
+ sections.push({
1717
+ id: "auto-stats",
1718
+ type: "stats",
1719
+ order: order++,
1720
+ content: { stats }
1721
+ });
1722
+ }
1723
+ const details = [];
1724
+ const genetics = toStr(cf?.genetics);
1725
+ const terpenes = toStr(cf?.terpenes);
1726
+ const effects = toStr(cf?.effects);
1727
+ const batchNumber = toStr(cf?.batch_number);
1728
+ const dateTested = toStr(cf?.date_tested);
1729
+ if (genetics) details.push({ label: "Genetics", value: genetics });
1730
+ if (terpenes) details.push({ label: "Terpenes", value: terpenes });
1731
+ if (effects) details.push({ label: "Effects", value: effects });
1732
+ if (batchNumber) details.push({ label: "Batch", value: batchNumber });
1733
+ if (dateTested) details.push({ label: "Tested", value: formatDate(dateTested) });
1734
+ if (details.length > 0) {
1735
+ sections.push({
1736
+ id: "auto-details",
1737
+ type: "stats",
1738
+ order: order++,
1739
+ content: { stats: details },
1740
+ config: { layout: "list" }
1741
+ });
1742
+ }
1743
+ if (description) {
1744
+ sections.push({
1745
+ id: "auto-description",
1746
+ type: "text",
1747
+ order: order++,
1748
+ content: { heading: "About", body: description }
1749
+ });
1750
+ }
1751
+ if (coa) {
1752
+ sections.push({
1753
+ id: "auto-coa",
1754
+ type: "coa_viewer",
1755
+ order: order++,
1756
+ content: { button_text: "View Lab Results" }
1757
+ });
1758
+ }
1759
+ if (ctaUrl) {
1760
+ sections.push({
1761
+ id: "auto-cta",
1762
+ type: "cta",
1763
+ order: order++,
1764
+ content: {
1765
+ buttons: [{ text: qr.landing_page.cta_text || "Shop Online", url: ctaUrl, style: "primary" }]
1766
+ }
1767
+ });
1768
+ }
1769
+ return sections;
1647
1770
  }
1648
1771
  function toNum(v) {
1649
1772
  if (v === "" || v == null) return null;
@@ -1654,13 +1777,6 @@ function toStr(v) {
1654
1777
  if (v == null || v === "") return null;
1655
1778
  return String(v);
1656
1779
  }
1657
- function strainBadgeColor(strain) {
1658
- const s = strain.toLowerCase();
1659
- if (s === "sativa") return "#22c55e";
1660
- if (s === "indica") return "#8b5cf6";
1661
- if (s === "hybrid") return "#f59e0b";
1662
- return "#6b7280";
1663
- }
1664
1780
  function formatDate(dateStr) {
1665
1781
  try {
1666
1782
  const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
@@ -1764,365 +1880,34 @@ function PageLayout({
1764
1880
  renderSection
1765
1881
  }) {
1766
1882
  const { landing_page: lp, store } = data;
1767
- const [showCOA, setShowCOA] = react.useState(false);
1768
1883
  const theme = {
1769
1884
  bg: lp.background_color || store?.theme?.background || "#050505",
1770
1885
  fg: lp.text_color || store?.theme?.foreground || "#fafafa",
1771
1886
  accent: lp.accent_color || store?.theme?.accent || "#E8E2D9",
1772
1887
  surface: store?.theme?.surface || "#111",
1773
- muted: store?.theme?.muted || "#888"
1888
+ muted: store?.theme?.muted || "#888",
1889
+ fontDisplay: store?.theme?.fontDisplay || void 0,
1890
+ fontBody: store?.theme?.fontBody || void 0
1774
1891
  };
1775
- const fontFamily = lp.font_family || "system-ui, -apple-system, sans-serif";
1892
+ const fontFamily = lp.font_family || theme.fontDisplay || "system-ui, -apple-system, sans-serif";
1776
1893
  const logoUrl = store?.logo_url;
1777
1894
  const sorted = [...lp.sections].sort((a, b) => a.order - b.order);
1778
1895
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
1779
1896
  lp.custom_css && /* @__PURE__ */ jsxRuntime.jsx("style", { children: lp.custom_css }),
1780
- logoUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1781
- "img",
1782
- {
1783
- src: logoUrl,
1784
- alt: store?.name || "Store",
1785
- style: { height: 40, objectFit: "contain" }
1786
- }
1787
- ) }),
1897
+ 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" } }) }),
1788
1898
  sorted.map((section) => {
1789
- const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(
1790
- DefaultSectionRenderer,
1791
- {
1792
- section,
1793
- data,
1794
- theme,
1795
- onShowCOA: () => setShowCOA(true)
1796
- },
1797
- section.id
1798
- );
1899
+ const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1799
1900
  if (renderSection) {
1800
1901
  return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
1801
1902
  }
1802
- return /* @__PURE__ */ jsxRuntime.jsx(
1803
- DefaultSectionRenderer,
1804
- {
1805
- section,
1806
- data,
1807
- theme,
1808
- onShowCOA: () => setShowCOA(true)
1809
- },
1810
- section.id
1811
- );
1903
+ return /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1812
1904
  }),
1813
1905
  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: [
1814
1906
  "Powered by ",
1815
1907
  store.name
1816
- ] }) }),
1817
- showCOA && data.coa && /* @__PURE__ */ jsxRuntime.jsxs(
1818
- "div",
1819
- {
1820
- style: {
1821
- position: "fixed",
1822
- inset: 0,
1823
- zIndex: 9999,
1824
- background: "rgba(0,0,0,0.9)",
1825
- display: "flex",
1826
- flexDirection: "column"
1827
- },
1828
- children: [
1829
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "1rem" }, children: [
1830
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fff", fontWeight: 600 }, children: data.coa.document_name || "Lab Results" }),
1831
- /* @__PURE__ */ jsxRuntime.jsx(
1832
- "button",
1833
- {
1834
- onClick: () => setShowCOA(false),
1835
- style: { background: "none", border: "none", color: "#fff", fontSize: "1.5rem", cursor: "pointer", padding: "0.5rem" },
1836
- children: "\u2715"
1837
- }
1838
- )
1839
- ] }),
1840
- /* @__PURE__ */ jsxRuntime.jsx(
1841
- "iframe",
1842
- {
1843
- src: data.coa.url,
1844
- style: { flex: 1, border: "none", background: "#fff" },
1845
- title: "Lab Results"
1846
- }
1847
- )
1848
- ]
1849
- }
1850
- )
1851
- ] });
1852
- }
1853
- function DefaultSectionRenderer({
1854
- section,
1855
- data,
1856
- theme,
1857
- onShowCOA
1858
- }) {
1859
- switch (section.type) {
1860
- case "hero":
1861
- return /* @__PURE__ */ jsxRuntime.jsx(HeroSection, { section, theme });
1862
- case "text":
1863
- return /* @__PURE__ */ jsxRuntime.jsx(TextSection, { section, theme });
1864
- case "image":
1865
- return /* @__PURE__ */ jsxRuntime.jsx(ImageSection, { section, theme });
1866
- case "video":
1867
- return /* @__PURE__ */ jsxRuntime.jsx(VideoSection, { section, theme });
1868
- case "gallery":
1869
- return /* @__PURE__ */ jsxRuntime.jsx(GallerySection, { section, theme });
1870
- case "cta":
1871
- return /* @__PURE__ */ jsxRuntime.jsx(CTASection, { section, theme });
1872
- case "stats":
1873
- return /* @__PURE__ */ jsxRuntime.jsx(StatsSection, { section, theme });
1874
- case "product_card":
1875
- return /* @__PURE__ */ jsxRuntime.jsx(ProductCardSection, { section, data, theme });
1876
- case "coa_viewer":
1877
- return /* @__PURE__ */ jsxRuntime.jsx(COAViewerSection, { section, data, theme, onShowCOA });
1878
- case "social_links":
1879
- return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
1880
- case "divider":
1881
- return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
1882
- default:
1883
- return null;
1884
- }
1885
- }
1886
- function HeroSection({ section, theme }) {
1887
- const { title, subtitle, background_image, cta_text, cta_url } = section.content;
1888
- return /* @__PURE__ */ jsxRuntime.jsxs(
1889
- "div",
1890
- {
1891
- style: {
1892
- position: "relative",
1893
- minHeight: "60vh",
1894
- display: "flex",
1895
- flexDirection: "column",
1896
- justifyContent: "center",
1897
- alignItems: "center",
1898
- textAlign: "center",
1899
- padding: "3rem 1.5rem",
1900
- backgroundImage: background_image ? `url(${background_image})` : void 0,
1901
- backgroundSize: "cover",
1902
- backgroundPosition: "center"
1903
- },
1904
- children: [
1905
- background_image && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
1906
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
1907
- title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "2.5rem", fontWeight: 700, margin: "0 0 1rem", lineHeight: 1.15, color: theme.fg }, children: title }),
1908
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", color: theme.muted, margin: "0 0 2rem", lineHeight: 1.6 }, children: subtitle }),
1909
- cta_text && cta_url && /* @__PURE__ */ jsxRuntime.jsx(
1910
- "a",
1911
- {
1912
- href: cta_url,
1913
- style: {
1914
- display: "inline-block",
1915
- padding: "0.875rem 2rem",
1916
- background: theme.accent,
1917
- color: theme.bg,
1918
- textDecoration: "none",
1919
- fontSize: "0.95rem",
1920
- fontWeight: 600,
1921
- borderRadius: 0
1922
- },
1923
- children: cta_text
1924
- }
1925
- )
1926
- ] })
1927
- ]
1928
- }
1929
- );
1930
- }
1931
- function TextSection({ section, theme }) {
1932
- const { heading, body } = section.content;
1933
- const align = section.config?.align || "left";
1934
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 640, margin: "0 auto", textAlign: align }, children: [
1935
- heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.5rem", fontWeight: 600, margin: "0 0 1rem", color: theme.fg }, children: heading }),
1936
- body && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: theme.muted, lineHeight: 1.7, fontSize: "0.95rem", whiteSpace: "pre-wrap" }, children: body })
1937
- ] });
1938
- }
1939
- function ImageSection({ section, theme }) {
1940
- const { url, alt, caption } = section.content;
1941
- const contained = section.config?.contained !== false;
1942
- if (!url) return null;
1943
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: contained ? "1.5rem" : 0, maxWidth: contained ? 640 : void 0, margin: contained ? "0 auto" : void 0 }, children: [
1944
- /* @__PURE__ */ jsxRuntime.jsx(
1945
- "img",
1946
- {
1947
- src: url,
1948
- alt: alt || "",
1949
- style: { width: "100%", display: "block", objectFit: "cover" }
1950
- }
1951
- ),
1952
- caption && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, textAlign: "center", marginTop: "0.75rem" }, children: caption })
1908
+ ] }) })
1953
1909
  ] });
1954
1910
  }
1955
- function VideoSection({ section, theme }) {
1956
- const { url, poster } = section.content;
1957
- if (!url) return null;
1958
- const isEmbed = url.includes("youtube") || url.includes("youtu.be") || url.includes("vimeo");
1959
- 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(
1960
- "iframe",
1961
- {
1962
- src: toEmbedUrl(url),
1963
- style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" },
1964
- allow: "autoplay; fullscreen",
1965
- title: "Video"
1966
- }
1967
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1968
- "video",
1969
- {
1970
- src: url,
1971
- poster,
1972
- controls: true,
1973
- style: { width: "100%", display: "block", background: theme.surface }
1974
- }
1975
- ) });
1976
- }
1977
- function GallerySection({ section, theme }) {
1978
- const { images } = section.content;
1979
- const columns = section.config?.columns || 3;
1980
- if (!images || images.length === 0) return null;
1981
- 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(
1982
- "img",
1983
- {
1984
- src: img.url,
1985
- alt: img.alt || "",
1986
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
1987
- }
1988
- ) }, i)) }) });
1989
- }
1990
- function CTASection({ section, theme }) {
1991
- const { buttons } = section.content;
1992
- if (!buttons || buttons.length === 0) return null;
1993
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", display: "flex", flexDirection: "column", gap: "0.75rem" }, children: buttons.map((btn, i) => {
1994
- const isPrimary = btn.style !== "outline";
1995
- return /* @__PURE__ */ jsxRuntime.jsx(
1996
- "a",
1997
- {
1998
- href: btn.url,
1999
- style: {
2000
- display: "block",
2001
- width: "100%",
2002
- padding: "0.875rem",
2003
- background: isPrimary ? theme.accent : "transparent",
2004
- color: isPrimary ? theme.bg : theme.fg,
2005
- border: isPrimary ? "none" : `1px solid ${theme.muted}`,
2006
- fontSize: "0.95rem",
2007
- fontWeight: 600,
2008
- textAlign: "center",
2009
- textDecoration: "none",
2010
- boxSizing: "border-box",
2011
- borderRadius: 0
2012
- },
2013
- children: btn.text
2014
- },
2015
- i
2016
- );
2017
- }) });
2018
- }
2019
- function StatsSection({ section, theme }) {
2020
- const { stats } = section.content;
2021
- if (!stats || stats.length === 0) return null;
2022
- const columns = Math.min(stats.length, 4);
2023
- 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)`, gap: "0.75rem" }, children: stats.map((stat, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, padding: "1rem", textAlign: "center" }, children: [
2024
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "1.25rem", fontWeight: 700, color: theme.fg }, children: stat.value }),
2025
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "0.7rem", color: theme.accent, textTransform: "uppercase", letterSpacing: "0.05em", marginTop: "0.25rem" }, children: stat.label })
2026
- ] }, i)) }) });
2027
- }
2028
- function ProductCardSection({ section, data, theme }) {
2029
- const product = data.product;
2030
- const c = section.content;
2031
- const name = c.name || product?.name || "";
2032
- const description = c.description || product?.description || "";
2033
- const price = c.price || null;
2034
- const imageUrl = c.image_url || product?.featured_image || null;
2035
- const url = c.url || null;
2036
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, overflow: "hidden" }, children: [
2037
- imageUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: "100%", aspectRatio: "1", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: imageUrl, alt: name, style: { width: "100%", height: "100%", objectFit: "cover", display: "block" } }) }),
2038
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "1.25rem" }, children: [
2039
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 0.5rem", color: theme.fg }, children: name }),
2040
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9rem", color: theme.muted, margin: "0 0 1rem", lineHeight: 1.5 }, children: description }),
2041
- price && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", fontWeight: 700, color: theme.accent, margin: "0 0 1rem" }, children: price }),
2042
- url && /* @__PURE__ */ jsxRuntime.jsx(
2043
- "a",
2044
- {
2045
- href: url,
2046
- style: {
2047
- display: "block",
2048
- width: "100%",
2049
- padding: "0.75rem",
2050
- background: theme.accent,
2051
- color: theme.bg,
2052
- textAlign: "center",
2053
- textDecoration: "none",
2054
- fontSize: "0.9rem",
2055
- fontWeight: 600,
2056
- boxSizing: "border-box",
2057
- borderRadius: 0
2058
- },
2059
- children: "View Product"
2060
- }
2061
- )
2062
- ] })
2063
- ] }) });
2064
- }
2065
- function COAViewerSection({
2066
- section,
2067
- data,
2068
- theme,
2069
- onShowCOA
2070
- }) {
2071
- const coa = data.coa;
2072
- const c = section.content;
2073
- if (!coa) return null;
2074
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2075
- "button",
2076
- {
2077
- onClick: onShowCOA,
2078
- style: {
2079
- width: "100%",
2080
- padding: "0.875rem",
2081
- background: theme.accent,
2082
- color: theme.bg,
2083
- border: "none",
2084
- fontSize: "0.95rem",
2085
- fontWeight: 600,
2086
- cursor: "pointer",
2087
- borderRadius: 0
2088
- },
2089
- children: c.button_text || "View Lab Results"
2090
- }
2091
- ) });
2092
- }
2093
- function SocialLinksSection({ section, theme }) {
2094
- const { links } = section.content;
2095
- if (!links || links.length === 0) return null;
2096
- 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(
2097
- "a",
2098
- {
2099
- href: link.url,
2100
- target: "_blank",
2101
- rel: "noopener noreferrer",
2102
- style: {
2103
- color: theme.muted,
2104
- textDecoration: "none",
2105
- fontSize: "0.85rem",
2106
- fontWeight: 500,
2107
- textTransform: "capitalize",
2108
- letterSpacing: "0.03em",
2109
- transition: "color 0.15s"
2110
- },
2111
- children: link.platform
2112
- },
2113
- i
2114
- )) });
2115
- }
2116
- function DividerSection({ theme }) {
2117
- 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.surface}`, margin: 0 } }) });
2118
- }
2119
- function toEmbedUrl(url) {
2120
- const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
2121
- if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
2122
- const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
2123
- if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
2124
- return url;
2125
- }
2126
1911
  var containerStyle2 = {
2127
1912
  minHeight: "100dvh",
2128
1913
  display: "flex",
@@ -2165,6 +1950,7 @@ exports.CartInitializer = CartInitializer;
2165
1950
  exports.LandingPage = LandingPage;
2166
1951
  exports.PixelInitializer = PixelInitializer;
2167
1952
  exports.QRLandingPage = QRLandingPage;
1953
+ exports.SectionRenderer = SectionRenderer;
2168
1954
  exports.WhaleContext = WhaleContext;
2169
1955
  exports.WhaleProvider = WhaleProvider;
2170
1956
  exports.useAnalytics = useAnalytics;