@neowhale/storefront 0.2.10 → 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,416 +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 rawPricing = product?.pricing_data;
1313
- const tiers = (Array.isArray(rawPricing) ? rawPricing : rawPricing?.tiers ?? []).sort((a, b) => (a.sort_order ?? 0) - (b.sort_order ?? 0));
1314
- const lowestPrice = tiers.length > 0 ? Math.min(...tiers.map((t) => t.default_price)) : null;
1315
- const handleCOAClick = react.useCallback(() => {
1316
- if (coa) setShowCOA(true);
1317
- }, [coa]);
1318
- const labelStyle = {
1319
- fontSize: 11,
1320
- fontWeight: 500,
1321
- textTransform: "uppercase",
1322
- letterSpacing: "0.25em",
1323
- color: `${theme.fg}40`
1324
- // 40 = 25% opacity
1325
- };
1326
- const dividerStyle = {
1327
- border: "none",
1328
- borderTop: `1px solid ${theme.fg}0A`,
1329
- // 0A = 4% opacity
1330
- margin: 0
1331
- };
1332
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1333
- minHeight: "100dvh",
1334
- background: theme.bg,
1335
- color: theme.fg,
1336
- fontFamily: theme.fontBody
1337
- }, children: [
1338
- /* @__PURE__ */ jsxRuntime.jsxs("nav", { style: {
1339
- padding: "1.25rem 1.5rem",
1340
- display: "flex",
1341
- alignItems: "center",
1342
- justifyContent: "space-between",
1343
- maxWidth: 1280,
1344
- margin: "0 auto"
1345
- }, children: [
1346
- 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", {}),
1347
- /* @__PURE__ */ jsxRuntime.jsx(
1348
- "a",
1349
- {
1350
- href: ctaUrl,
1351
- style: {
1352
- fontSize: 11,
1353
- fontWeight: 500,
1354
- textTransform: "uppercase",
1355
- letterSpacing: "0.15em",
1356
- color: theme.fg,
1357
- textDecoration: "none",
1358
- padding: "0.5rem 1.25rem",
1359
- border: `1px solid ${theme.fg}10`,
1360
- transition: "border-color 0.3s"
1361
- },
1362
- children: "Shop"
1363
- }
1364
- )
1365
- ] }),
1366
- /* @__PURE__ */ jsxRuntime.jsxs("main", { style: { maxWidth: 560, margin: "0 auto", padding: "0 1.5rem 3rem" }, children: [
1367
- productImage && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1368
- width: "100%",
1369
- aspectRatio: "1",
1370
- overflow: "hidden",
1371
- background: theme.surfaceLight,
1372
- display: "flex",
1373
- alignItems: "center",
1374
- justifyContent: "center",
1375
- padding: "2rem"
1376
- }, children: /* @__PURE__ */ jsxRuntime.jsx(
1377
- "img",
1378
- {
1379
- src: productImage,
1380
- alt: productName,
1381
- style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" }
1382
- }
1383
- ) }),
1384
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.5rem", display: "flex", gap: "0.75rem", alignItems: "center" }, children: [
1385
- categoryName && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1386
- fontSize: 11,
1387
- fontWeight: 500,
1388
- color: theme.accent,
1389
- textTransform: "uppercase",
1390
- letterSpacing: "0.15em"
1391
- }, children: categoryName }),
1392
- strainType && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1393
- fontSize: 10,
1394
- fontWeight: 600,
1395
- textTransform: "uppercase",
1396
- letterSpacing: "0.08em",
1397
- padding: "0.2rem 0.5rem",
1398
- background: strainBadgeColor(strainType),
1399
- color: "#fff"
1400
- }, children: strainType })
1401
- ] }),
1402
- /* @__PURE__ */ jsxRuntime.jsx("h1", { style: {
1403
- fontFamily: theme.fontDisplay,
1404
- fontSize: "clamp(2rem, 8vw, 3rem)",
1405
- fontWeight: 300,
1406
- margin: "0.5rem 0 0",
1407
- lineHeight: 1.1,
1408
- letterSpacing: "-0.02em"
1409
- }, children: productName }),
1410
- tagline && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
1411
- fontFamily: theme.fontDisplay,
1412
- fontSize: "1.1rem",
1413
- fontWeight: 300,
1414
- color: `${theme.fg}80`,
1415
- margin: "0.5rem 0 0"
1416
- }, children: tagline }),
1417
- lowestPrice != null && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: {
1418
- fontFamily: theme.fontDisplay,
1419
- fontSize: "1.5rem",
1420
- fontWeight: 300,
1421
- margin: "0.75rem 0 0"
1422
- }, children: [
1423
- "$",
1424
- lowestPrice.toFixed(2),
1425
- tiers.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.8rem", color: `${theme.fg}50`, marginLeft: "0.5rem", fontWeight: 400 }, children: "and up" })
1426
- ] }),
1427
- renderProduct ? renderProduct(data) : null,
1428
- (thca != null || thc != null || cbd != null) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1429
- marginTop: "1.75rem",
1430
- border: `1px solid ${theme.fg}0F`,
1431
- display: "grid",
1432
- gridTemplateColumns: `repeat(${[thca, thc, cbd].filter((v) => v != null).length}, 1fr)`
1433
- }, children: [
1434
- thca != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "THCa", value: thca, theme, showBorder: thc != null || cbd != null }),
1435
- thc != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "\u03949 THC", value: thc, theme, showBorder: cbd != null }),
1436
- cbd != null && /* @__PURE__ */ jsxRuntime.jsx(CannabinoidStat, { label: "CBD", value: cbd, theme, showBorder: false })
1437
- ] }),
1438
- tiers.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.5rem" }, children: [
1439
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "Pricing" }),
1440
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 1 }, children: tiers.map((tier) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1441
- flex: "1 1 0",
1442
- minWidth: 90,
1443
- padding: "0.75rem 0.5rem",
1444
- background: theme.surfaceLight,
1445
- textAlign: "center"
1446
- }, children: [
1447
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "1rem", fontWeight: 300, fontFamily: theme.fontDisplay }, children: [
1448
- "$",
1449
- tier.default_price.toFixed(2)
1450
- ] }),
1451
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 10, color: `${theme.fg}60`, letterSpacing: "0.1em", textTransform: "uppercase", marginTop: 2 }, children: tier.label })
1452
- ] }, tier.id)) })
1453
- ] }),
1454
- (genetics || terpenes || effects || batchNumber || dateTested) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1455
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "Details" }),
1456
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1457
- genetics && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1458
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Genetics", value: genetics, theme }),
1459
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1460
- ] }),
1461
- terpenes && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1462
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Terpenes", value: terpenes, theme }),
1463
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1464
- ] }),
1465
- effects && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1466
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Effects", value: effects, theme }),
1467
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1468
- ] }),
1469
- batchNumber && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1470
- /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Batch", value: batchNumber, theme }),
1471
- /* @__PURE__ */ jsxRuntime.jsx("hr", { style: dividerStyle })
1472
- ] }),
1473
- dateTested && /* @__PURE__ */ jsxRuntime.jsx(DetailRow, { label: "Tested", value: formatDate(dateTested), theme })
1474
- ] })
1475
- ] }),
1476
- description && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1477
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "About" }),
1478
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
1479
- fontSize: "0.9rem",
1480
- fontWeight: 300,
1481
- color: `${theme.fg}99`,
1482
- lineHeight: 1.7,
1483
- margin: 0
1484
- }, children: description })
1485
- ] }),
1486
- coa && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1.75rem" }, children: [
1487
- /* @__PURE__ */ jsxRuntime.jsx("p", { style: { ...labelStyle, marginBottom: "0.75rem" }, children: "Lab Results" }),
1488
- batchNumber && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 12, color: `${theme.fg}60`, margin: "0 0 0.5rem", letterSpacing: "0.05em" }, children: [
1489
- "Batch ",
1490
- batchNumber,
1491
- dateTested ? ` \xB7 ${formatDate(dateTested)}` : ""
1492
- ] }),
1493
- /* @__PURE__ */ jsxRuntime.jsxs(
1494
- "button",
1495
- {
1496
- onClick: handleCOAClick,
1497
- style: {
1498
- width: "100%",
1499
- position: "relative",
1500
- height: 180,
1501
- border: `1px solid ${theme.fg}0F`,
1502
- background: theme.surface,
1503
- cursor: "pointer",
1504
- overflow: "hidden",
1505
- padding: 0,
1506
- display: "block"
1507
- },
1508
- children: [
1509
- /* @__PURE__ */ jsxRuntime.jsx(
1510
- "iframe",
1511
- {
1512
- src: coa.url,
1513
- style: {
1514
- width: "200%",
1515
- height: "200%",
1516
- border: "none",
1517
- transform: "scale(0.5)",
1518
- transformOrigin: "top left",
1519
- pointerEvents: "none"
1520
- },
1521
- title: "COA Preview",
1522
- tabIndex: -1
1523
- }
1524
- ),
1525
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1526
- position: "absolute",
1527
- inset: 0,
1528
- background: `linear-gradient(to top, ${theme.bg}E6 0%, transparent 60%)`,
1529
- display: "flex",
1530
- alignItems: "flex-end",
1531
- justifyContent: "center",
1532
- paddingBottom: "1.25rem"
1533
- }, children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1534
- fontSize: 11,
1535
- fontWeight: 500,
1536
- textTransform: "uppercase",
1537
- letterSpacing: "0.2em",
1538
- color: theme.accent
1539
- }, children: "View Certificate of Analysis" }) })
1540
- ]
1541
- }
1542
- )
1543
- ] }),
1544
- renderCOA ? renderCOA(data) : null,
1545
- /* @__PURE__ */ jsxRuntime.jsx(
1546
- "a",
1547
- {
1548
- href: ctaUrl,
1549
- style: {
1550
- display: "block",
1551
- width: "100%",
1552
- marginTop: "2rem",
1553
- padding: "1rem",
1554
- background: theme.fg,
1555
- color: theme.bg,
1556
- fontFamily: theme.fontDisplay,
1557
- fontSize: "0.85rem",
1558
- fontWeight: 500,
1559
- textAlign: "center",
1560
- textDecoration: "none",
1561
- letterSpacing: "0.08em",
1562
- textTransform: "uppercase",
1563
- boxSizing: "border-box"
1564
- },
1565
- children: "Shop Online"
1566
- }
1567
- ),
1568
- /* @__PURE__ */ jsxRuntime.jsxs("footer", { style: {
1569
- marginTop: "3rem",
1570
- paddingTop: "1.5rem",
1571
- borderTop: `1px solid ${theme.fg}0A`,
1572
- textAlign: "center"
1573
- }, children: [
1574
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "center", gap: "2rem", marginBottom: "0.75rem" }, children: [
1575
- coa && /* @__PURE__ */ jsxRuntime.jsx(FooterLabel, { text: "Lab Verified" }),
1576
- /* @__PURE__ */ jsxRuntime.jsx(FooterLabel, { text: "Authentic Product" })
1577
- ] }),
1578
- store?.name && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: 11, color: `${theme.fg}40`, margin: "0.5rem 0 0", letterSpacing: "0.1em" }, children: [
1579
- store.name,
1580
- store?.tagline ? ` \u2014 ${store.tagline}` : ""
1581
- ] })
1582
- ] })
1583
- ] }),
1584
- showCOA && coa && /* @__PURE__ */ jsxRuntime.jsx(COAModal, { coa, theme, onClose: () => setShowCOA(false) })
1585
- ] });
1586
- }
1587
- function CannabinoidStat({ label, value, theme, showBorder }) {
1588
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1589
- padding: "1.25rem 0.5rem",
1590
- textAlign: "center",
1591
- borderRight: showBorder ? `1px solid ${theme.fg}0F` : void 0
1592
- }, children: [
1593
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1594
- fontFamily: theme.fontDisplay,
1595
- fontSize: "clamp(1.5rem, 5vw, 2rem)",
1596
- fontWeight: 300,
1597
- lineHeight: 1,
1598
- color: theme.fg
1599
- }, children: [
1600
- value.toFixed(value >= 1 ? 1 : 2),
1601
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.6em", marginLeft: 1 }, children: "%" })
1602
- ] }),
1603
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
1604
- fontSize: 11,
1605
- fontWeight: 500,
1606
- textTransform: "uppercase",
1607
- letterSpacing: "0.25em",
1608
- color: theme.accent,
1609
- marginTop: "0.5rem"
1610
- }, children: label })
1611
- ] });
1612
- }
1613
- function DetailRow({ label, value, theme }) {
1614
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1615
- display: "flex",
1616
- justifyContent: "space-between",
1617
- alignItems: "baseline",
1618
- padding: "0.625rem 0"
1619
- }, children: [
1620
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1621
- fontSize: 12,
1622
- textTransform: "uppercase",
1623
- letterSpacing: "0.15em",
1624
- color: `${theme.fg}66`
1625
- }, children: label }),
1626
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1627
- fontSize: 14,
1628
- fontWeight: 300,
1629
- color: `${theme.fg}CC`
1630
- }, children: value })
1631
- ] });
1632
- }
1633
- function FooterLabel({ text }) {
1634
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1635
- fontSize: 10,
1636
- textTransform: "uppercase",
1637
- letterSpacing: "0.15em",
1638
- color: "rgba(255,255,255,0.25)"
1639
- }, children: text });
1640
- }
1641
- function COAModal({ coa, theme, onClose }) {
1642
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 9999, background: "rgba(0,0,0,0.95)", display: "flex", flexDirection: "column" }, children: [
1643
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
1644
- display: "flex",
1645
- justifyContent: "space-between",
1646
- alignItems: "center",
1647
- padding: "0.75rem 1rem",
1648
- borderBottom: `1px solid ${theme.fg}10`
1649
- }, children: [
1650
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
1651
- color: "#fff",
1652
- fontFamily: theme.fontDisplay,
1653
- fontWeight: 500,
1654
- fontSize: "0.85rem",
1655
- letterSpacing: "-0.01em"
1656
- }, children: coa.document_name || "Lab Results" }),
1657
- /* @__PURE__ */ jsxRuntime.jsx(
1658
- "button",
1659
- {
1660
- onClick: onClose,
1661
- style: {
1662
- background: `${theme.fg}10`,
1663
- border: "none",
1664
- color: "#fff",
1665
- fontSize: "0.85rem",
1666
- cursor: "pointer",
1667
- padding: "0.375rem 0.75rem",
1668
- letterSpacing: "0.05em"
1669
- },
1670
- children: "Close"
1671
- }
1672
- )
1673
- ] }),
1674
- /* @__PURE__ */ jsxRuntime.jsx("iframe", { src: coa.url, style: { flex: 1, border: "none", background: "#fff" }, title: "Lab Results" })
1675
- ] });
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;
1676
1770
  }
1677
1771
  function toNum(v) {
1678
1772
  if (v === "" || v == null) return null;
@@ -1683,13 +1777,6 @@ function toStr(v) {
1683
1777
  if (v == null || v === "") return null;
1684
1778
  return String(v);
1685
1779
  }
1686
- function strainBadgeColor(strain) {
1687
- const s = strain.toLowerCase();
1688
- if (s === "sativa") return "#22c55e";
1689
- if (s === "indica") return "#8b5cf6";
1690
- if (s === "hybrid") return "#f59e0b";
1691
- return "#6b7280";
1692
- }
1693
1780
  function formatDate(dateStr) {
1694
1781
  try {
1695
1782
  const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
@@ -1793,365 +1880,34 @@ function PageLayout({
1793
1880
  renderSection
1794
1881
  }) {
1795
1882
  const { landing_page: lp, store } = data;
1796
- const [showCOA, setShowCOA] = react.useState(false);
1797
1883
  const theme = {
1798
1884
  bg: lp.background_color || store?.theme?.background || "#050505",
1799
1885
  fg: lp.text_color || store?.theme?.foreground || "#fafafa",
1800
1886
  accent: lp.accent_color || store?.theme?.accent || "#E8E2D9",
1801
1887
  surface: store?.theme?.surface || "#111",
1802
- 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
1803
1891
  };
1804
- 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";
1805
1893
  const logoUrl = store?.logo_url;
1806
1894
  const sorted = [...lp.sections].sort((a, b) => a.order - b.order);
1807
1895
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minHeight: "100dvh", background: theme.bg, color: theme.fg, fontFamily }, children: [
1808
1896
  lp.custom_css && /* @__PURE__ */ jsxRuntime.jsx("style", { children: lp.custom_css }),
1809
- logoUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1810
- "img",
1811
- {
1812
- src: logoUrl,
1813
- alt: store?.name || "Store",
1814
- style: { height: 40, objectFit: "contain" }
1815
- }
1816
- ) }),
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" } }) }),
1817
1898
  sorted.map((section) => {
1818
- const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(
1819
- DefaultSectionRenderer,
1820
- {
1821
- section,
1822
- data,
1823
- theme,
1824
- onShowCOA: () => setShowCOA(true)
1825
- },
1826
- section.id
1827
- );
1899
+ const defaultRenderer = () => /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1828
1900
  if (renderSection) {
1829
1901
  return /* @__PURE__ */ jsxRuntime.jsx("div", { children: renderSection(section, defaultRenderer) }, section.id);
1830
1902
  }
1831
- return /* @__PURE__ */ jsxRuntime.jsx(
1832
- DefaultSectionRenderer,
1833
- {
1834
- section,
1835
- data,
1836
- theme,
1837
- onShowCOA: () => setShowCOA(true)
1838
- },
1839
- section.id
1840
- );
1903
+ return /* @__PURE__ */ jsxRuntime.jsx(SectionRenderer, { section, data, theme }, section.id);
1841
1904
  }),
1842
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: [
1843
1906
  "Powered by ",
1844
1907
  store.name
1845
- ] }) }),
1846
- showCOA && data.coa && /* @__PURE__ */ jsxRuntime.jsxs(
1847
- "div",
1848
- {
1849
- style: {
1850
- position: "fixed",
1851
- inset: 0,
1852
- zIndex: 9999,
1853
- background: "rgba(0,0,0,0.9)",
1854
- display: "flex",
1855
- flexDirection: "column"
1856
- },
1857
- children: [
1858
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "1rem" }, children: [
1859
- /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: "#fff", fontWeight: 600 }, children: data.coa.document_name || "Lab Results" }),
1860
- /* @__PURE__ */ jsxRuntime.jsx(
1861
- "button",
1862
- {
1863
- onClick: () => setShowCOA(false),
1864
- style: { background: "none", border: "none", color: "#fff", fontSize: "1.5rem", cursor: "pointer", padding: "0.5rem" },
1865
- children: "\u2715"
1866
- }
1867
- )
1868
- ] }),
1869
- /* @__PURE__ */ jsxRuntime.jsx(
1870
- "iframe",
1871
- {
1872
- src: data.coa.url,
1873
- style: { flex: 1, border: "none", background: "#fff" },
1874
- title: "Lab Results"
1875
- }
1876
- )
1877
- ]
1878
- }
1879
- )
1880
- ] });
1881
- }
1882
- function DefaultSectionRenderer({
1883
- section,
1884
- data,
1885
- theme,
1886
- onShowCOA
1887
- }) {
1888
- switch (section.type) {
1889
- case "hero":
1890
- return /* @__PURE__ */ jsxRuntime.jsx(HeroSection, { section, theme });
1891
- case "text":
1892
- return /* @__PURE__ */ jsxRuntime.jsx(TextSection, { section, theme });
1893
- case "image":
1894
- return /* @__PURE__ */ jsxRuntime.jsx(ImageSection, { section, theme });
1895
- case "video":
1896
- return /* @__PURE__ */ jsxRuntime.jsx(VideoSection, { section, theme });
1897
- case "gallery":
1898
- return /* @__PURE__ */ jsxRuntime.jsx(GallerySection, { section, theme });
1899
- case "cta":
1900
- return /* @__PURE__ */ jsxRuntime.jsx(CTASection, { section, theme });
1901
- case "stats":
1902
- return /* @__PURE__ */ jsxRuntime.jsx(StatsSection, { section, theme });
1903
- case "product_card":
1904
- return /* @__PURE__ */ jsxRuntime.jsx(ProductCardSection, { section, data, theme });
1905
- case "coa_viewer":
1906
- return /* @__PURE__ */ jsxRuntime.jsx(COAViewerSection, { section, data, theme, onShowCOA });
1907
- case "social_links":
1908
- return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
1909
- case "divider":
1910
- return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
1911
- default:
1912
- return null;
1913
- }
1914
- }
1915
- function HeroSection({ section, theme }) {
1916
- const { title, subtitle, background_image, cta_text, cta_url } = section.content;
1917
- return /* @__PURE__ */ jsxRuntime.jsxs(
1918
- "div",
1919
- {
1920
- style: {
1921
- position: "relative",
1922
- minHeight: "60vh",
1923
- display: "flex",
1924
- flexDirection: "column",
1925
- justifyContent: "center",
1926
- alignItems: "center",
1927
- textAlign: "center",
1928
- padding: "3rem 1.5rem",
1929
- backgroundImage: background_image ? `url(${background_image})` : void 0,
1930
- backgroundSize: "cover",
1931
- backgroundPosition: "center"
1932
- },
1933
- children: [
1934
- background_image && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, background: "rgba(0,0,0,0.5)" } }),
1935
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", zIndex: 1, maxWidth: 640 }, children: [
1936
- title && /* @__PURE__ */ jsxRuntime.jsx("h1", { style: { fontSize: "2.5rem", fontWeight: 700, margin: "0 0 1rem", lineHeight: 1.15, color: theme.fg }, children: title }),
1937
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", color: theme.muted, margin: "0 0 2rem", lineHeight: 1.6 }, children: subtitle }),
1938
- cta_text && cta_url && /* @__PURE__ */ jsxRuntime.jsx(
1939
- "a",
1940
- {
1941
- href: cta_url,
1942
- style: {
1943
- display: "inline-block",
1944
- padding: "0.875rem 2rem",
1945
- background: theme.accent,
1946
- color: theme.bg,
1947
- textDecoration: "none",
1948
- fontSize: "0.95rem",
1949
- fontWeight: 600,
1950
- borderRadius: 0
1951
- },
1952
- children: cta_text
1953
- }
1954
- )
1955
- ] })
1956
- ]
1957
- }
1958
- );
1959
- }
1960
- function TextSection({ section, theme }) {
1961
- const { heading, body } = section.content;
1962
- const align = section.config?.align || "left";
1963
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 640, margin: "0 auto", textAlign: align }, children: [
1964
- heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: { fontSize: "1.5rem", fontWeight: 600, margin: "0 0 1rem", color: theme.fg }, children: heading }),
1965
- body && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { color: theme.muted, lineHeight: 1.7, fontSize: "0.95rem", whiteSpace: "pre-wrap" }, children: body })
1966
- ] });
1967
- }
1968
- function ImageSection({ section, theme }) {
1969
- const { url, alt, caption } = section.content;
1970
- const contained = section.config?.contained !== false;
1971
- if (!url) return null;
1972
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: contained ? "1.5rem" : 0, maxWidth: contained ? 640 : void 0, margin: contained ? "0 auto" : void 0 }, children: [
1973
- /* @__PURE__ */ jsxRuntime.jsx(
1974
- "img",
1975
- {
1976
- src: url,
1977
- alt: alt || "",
1978
- style: { width: "100%", display: "block", objectFit: "cover" }
1979
- }
1980
- ),
1981
- caption && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, textAlign: "center", marginTop: "0.75rem" }, children: caption })
1908
+ ] }) })
1982
1909
  ] });
1983
1910
  }
1984
- function VideoSection({ section, theme }) {
1985
- const { url, poster } = section.content;
1986
- if (!url) return null;
1987
- const isEmbed = url.includes("youtube") || url.includes("youtu.be") || url.includes("vimeo");
1988
- 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(
1989
- "iframe",
1990
- {
1991
- src: toEmbedUrl(url),
1992
- style: { position: "absolute", top: 0, left: 0, width: "100%", height: "100%", border: "none" },
1993
- allow: "autoplay; fullscreen",
1994
- title: "Video"
1995
- }
1996
- ) }) : /* @__PURE__ */ jsxRuntime.jsx(
1997
- "video",
1998
- {
1999
- src: url,
2000
- poster,
2001
- controls: true,
2002
- style: { width: "100%", display: "block", background: theme.surface }
2003
- }
2004
- ) });
2005
- }
2006
- function GallerySection({ section, theme }) {
2007
- const { images } = section.content;
2008
- const columns = section.config?.columns || 3;
2009
- if (!images || images.length === 0) return null;
2010
- 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(
2011
- "img",
2012
- {
2013
- src: img.url,
2014
- alt: img.alt || "",
2015
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
2016
- }
2017
- ) }, i)) }) });
2018
- }
2019
- function CTASection({ section, theme }) {
2020
- const { buttons } = section.content;
2021
- if (!buttons || buttons.length === 0) return null;
2022
- 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) => {
2023
- const isPrimary = btn.style !== "outline";
2024
- return /* @__PURE__ */ jsxRuntime.jsx(
2025
- "a",
2026
- {
2027
- href: btn.url,
2028
- style: {
2029
- display: "block",
2030
- width: "100%",
2031
- padding: "0.875rem",
2032
- background: isPrimary ? theme.accent : "transparent",
2033
- color: isPrimary ? theme.bg : theme.fg,
2034
- border: isPrimary ? "none" : `1px solid ${theme.muted}`,
2035
- fontSize: "0.95rem",
2036
- fontWeight: 600,
2037
- textAlign: "center",
2038
- textDecoration: "none",
2039
- boxSizing: "border-box",
2040
- borderRadius: 0
2041
- },
2042
- children: btn.text
2043
- },
2044
- i
2045
- );
2046
- }) });
2047
- }
2048
- function StatsSection({ section, theme }) {
2049
- const { stats } = section.content;
2050
- if (!stats || stats.length === 0) return null;
2051
- const columns = Math.min(stats.length, 4);
2052
- 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: [
2053
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "1.25rem", fontWeight: 700, color: theme.fg }, children: stat.value }),
2054
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: "0.7rem", color: theme.accent, textTransform: "uppercase", letterSpacing: "0.05em", marginTop: "0.25rem" }, children: stat.label })
2055
- ] }, i)) }) });
2056
- }
2057
- function ProductCardSection({ section, data, theme }) {
2058
- const product = data.product;
2059
- const c = section.content;
2060
- const name = c.name || product?.name || "";
2061
- const description = c.description || product?.description || "";
2062
- const price = c.price || null;
2063
- const imageUrl = c.image_url || product?.featured_image || null;
2064
- const url = c.url || null;
2065
- 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: [
2066
- 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" } }) }),
2067
- /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "1.25rem" }, children: [
2068
- /* @__PURE__ */ jsxRuntime.jsx("h3", { style: { fontSize: "1.25rem", fontWeight: 600, margin: "0 0 0.5rem", color: theme.fg }, children: name }),
2069
- description && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.9rem", color: theme.muted, margin: "0 0 1rem", lineHeight: 1.5 }, children: description }),
2070
- price && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "1.125rem", fontWeight: 700, color: theme.accent, margin: "0 0 1rem" }, children: price }),
2071
- url && /* @__PURE__ */ jsxRuntime.jsx(
2072
- "a",
2073
- {
2074
- href: url,
2075
- style: {
2076
- display: "block",
2077
- width: "100%",
2078
- padding: "0.75rem",
2079
- background: theme.accent,
2080
- color: theme.bg,
2081
- textAlign: "center",
2082
- textDecoration: "none",
2083
- fontSize: "0.9rem",
2084
- fontWeight: 600,
2085
- boxSizing: "border-box",
2086
- borderRadius: 0
2087
- },
2088
- children: "View Product"
2089
- }
2090
- )
2091
- ] })
2092
- ] }) });
2093
- }
2094
- function COAViewerSection({
2095
- section,
2096
- data,
2097
- theme,
2098
- onShowCOA
2099
- }) {
2100
- const coa = data.coa;
2101
- const c = section.content;
2102
- if (!coa) return null;
2103
- return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", maxWidth: 480, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2104
- "button",
2105
- {
2106
- onClick: onShowCOA,
2107
- style: {
2108
- width: "100%",
2109
- padding: "0.875rem",
2110
- background: theme.accent,
2111
- color: theme.bg,
2112
- border: "none",
2113
- fontSize: "0.95rem",
2114
- fontWeight: 600,
2115
- cursor: "pointer",
2116
- borderRadius: 0
2117
- },
2118
- children: c.button_text || "View Lab Results"
2119
- }
2120
- ) });
2121
- }
2122
- function SocialLinksSection({ section, theme }) {
2123
- const { links } = section.content;
2124
- if (!links || links.length === 0) return null;
2125
- 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(
2126
- "a",
2127
- {
2128
- href: link.url,
2129
- target: "_blank",
2130
- rel: "noopener noreferrer",
2131
- style: {
2132
- color: theme.muted,
2133
- textDecoration: "none",
2134
- fontSize: "0.85rem",
2135
- fontWeight: 500,
2136
- textTransform: "capitalize",
2137
- letterSpacing: "0.03em",
2138
- transition: "color 0.15s"
2139
- },
2140
- children: link.platform
2141
- },
2142
- i
2143
- )) });
2144
- }
2145
- function DividerSection({ theme }) {
2146
- 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 } }) });
2147
- }
2148
- function toEmbedUrl(url) {
2149
- const ytMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
2150
- if (ytMatch) return `https://www.youtube.com/embed/${ytMatch[1]}`;
2151
- const vimeoMatch = url.match(/vimeo\.com\/(\d+)/);
2152
- if (vimeoMatch) return `https://player.vimeo.com/video/${vimeoMatch[1]}`;
2153
- return url;
2154
- }
2155
1911
  var containerStyle2 = {
2156
1912
  minHeight: "100dvh",
2157
1913
  display: "flex",
@@ -2194,6 +1950,7 @@ exports.CartInitializer = CartInitializer;
2194
1950
  exports.LandingPage = LandingPage;
2195
1951
  exports.PixelInitializer = PixelInitializer;
2196
1952
  exports.QRLandingPage = QRLandingPage;
1953
+ exports.SectionRenderer = SectionRenderer;
2197
1954
  exports.WhaleContext = WhaleContext;
2198
1955
  exports.WhaleProvider = WhaleProvider;
2199
1956
  exports.useAnalytics = useAnalytics;