shared-features 0.1.7 → 0.1.9

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.
@@ -1168,17 +1168,63 @@ function AdUpdateModal({
1168
1168
  }
1169
1169
  ) });
1170
1170
  }
1171
+ const DISMISSED_BANNERS_KEY = "sf_dismissed_banners";
1171
1172
  function AdBanner({
1172
1173
  placement = "home_banner",
1173
1174
  rotationInterval = 1e4,
1174
1175
  maxCampaigns = 5,
1176
+ dismissible = false,
1177
+ dismissDuration = "session",
1175
1178
  className,
1176
1179
  style
1177
1180
  }) {
1178
1181
  const [currentIndex, setCurrentIndex] = react.useState(0);
1179
1182
  const [isAnimating, setIsAnimating] = react.useState(false);
1180
1183
  const [progress, setProgress] = react.useState(0);
1184
+ const [isDismissed, setIsDismissed] = react.useState(false);
1181
1185
  const trackedImpressions = react.useRef(/* @__PURE__ */ new Set());
1186
+ react.useEffect(() => {
1187
+ if (!dismissible) return;
1188
+ const checkDismissed = () => {
1189
+ if (dismissDuration === "persistent") {
1190
+ try {
1191
+ const dismissed = localStorage.getItem(DISMISSED_BANNERS_KEY);
1192
+ if (dismissed) {
1193
+ const dismissedPlacements = JSON.parse(dismissed);
1194
+ if (dismissedPlacements.includes(placement)) {
1195
+ setIsDismissed(true);
1196
+ }
1197
+ }
1198
+ } catch {
1199
+ }
1200
+ } else {
1201
+ try {
1202
+ const dismissed = sessionStorage.getItem(DISMISSED_BANNERS_KEY);
1203
+ if (dismissed) {
1204
+ const dismissedPlacements = JSON.parse(dismissed);
1205
+ if (dismissedPlacements.includes(placement)) {
1206
+ setIsDismissed(true);
1207
+ }
1208
+ }
1209
+ } catch {
1210
+ }
1211
+ }
1212
+ };
1213
+ checkDismissed();
1214
+ }, [dismissible, dismissDuration, placement]);
1215
+ const handleDismiss = react.useCallback(() => {
1216
+ setIsDismissed(true);
1217
+ const storage = dismissDuration === "persistent" ? localStorage : sessionStorage;
1218
+ try {
1219
+ const existing = storage.getItem(DISMISSED_BANNERS_KEY);
1220
+ const dismissedPlacements = existing ? JSON.parse(existing) : [];
1221
+ if (!dismissedPlacements.includes(placement)) {
1222
+ dismissedPlacements.push(placement);
1223
+ storage.setItem(DISMISSED_BANNERS_KEY, JSON.stringify(dismissedPlacements));
1224
+ }
1225
+ } catch {
1226
+ }
1227
+ }, [dismissDuration, placement]);
1182
1228
  const {
1183
1229
  campaigns,
1184
1230
  loading,
@@ -1230,7 +1276,7 @@ function AdBanner({
1230
1276
  setTimeout(() => setIsAnimating(false), 50);
1231
1277
  }, 200);
1232
1278
  }, [currentIndex]);
1233
- if (loading || campaigns.length === 0) return null;
1279
+ if (loading || campaigns.length === 0 || isDismissed) return null;
1234
1280
  const campaign = campaigns[currentIndex];
1235
1281
  if (!campaign) return null;
1236
1282
  const { product } = campaign;
@@ -1251,6 +1297,24 @@ function AdBanner({
1251
1297
  ...style
1252
1298
  },
1253
1299
  children: [
1300
+ dismissible && /* @__PURE__ */ jsxRuntime.jsx(
1301
+ themes.IconButton,
1302
+ {
1303
+ size: "1",
1304
+ variant: "ghost",
1305
+ color: "gray",
1306
+ onClick: handleDismiss,
1307
+ style: {
1308
+ position: "absolute",
1309
+ top: 8,
1310
+ right: 8,
1311
+ zIndex: 10,
1312
+ cursor: "pointer"
1313
+ },
1314
+ "aria-label": "Close banner",
1315
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16 })
1316
+ }
1317
+ ),
1254
1318
  /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { style: { height: 3, background: displayColor } }),
1255
1319
  /* @__PURE__ */ jsxRuntime.jsxs(
1256
1320
  themes.Box,
@@ -1408,6 +1472,476 @@ function AdBanner({
1408
1472
  }
1409
1473
  );
1410
1474
  }
1475
+ const DISMISSED_TOPBAR_KEY = "sf_topbar_dismissed";
1476
+ function TopbarAdBanner({
1477
+ placement = "topbar_banner",
1478
+ rotationInterval = 2e4,
1479
+ maxCampaigns = 5,
1480
+ className,
1481
+ style
1482
+ }) {
1483
+ const [currentIndex, setCurrentIndex] = react.useState(0);
1484
+ const [isAnimating, setIsAnimating] = react.useState(false);
1485
+ const [isDismissed, setIsDismissed] = react.useState(false);
1486
+ const trackedImpressions = react.useRef(/* @__PURE__ */ new Set());
1487
+ const timerRef = react.useRef(null);
1488
+ react.useEffect(() => {
1489
+ try {
1490
+ const dismissed = sessionStorage.getItem(DISMISSED_TOPBAR_KEY);
1491
+ if (dismissed === "true") {
1492
+ setIsDismissed(true);
1493
+ }
1494
+ } catch {
1495
+ }
1496
+ }, []);
1497
+ const handleDismiss = react.useCallback(() => {
1498
+ setIsDismissed(true);
1499
+ try {
1500
+ sessionStorage.setItem(DISMISSED_TOPBAR_KEY, "true");
1501
+ } catch {
1502
+ }
1503
+ }, []);
1504
+ const {
1505
+ campaigns,
1506
+ loading,
1507
+ recordImpression,
1508
+ recordClick
1509
+ } = useCommonFeatures.useCampaigns({ placement, maxCampaigns });
1510
+ react.useEffect(() => {
1511
+ if (campaigns.length === 0) return;
1512
+ const campaign2 = campaigns[currentIndex];
1513
+ if (!campaign2 || trackedImpressions.current.has(campaign2.id)) return;
1514
+ trackedImpressions.current.add(campaign2.id);
1515
+ recordImpression(campaign2);
1516
+ }, [currentIndex, campaigns, recordImpression]);
1517
+ react.useEffect(() => {
1518
+ if (campaigns.length <= 1) return;
1519
+ timerRef.current = setInterval(() => {
1520
+ setIsAnimating(true);
1521
+ setTimeout(() => {
1522
+ setCurrentIndex((prev) => (prev + 1) % campaigns.length);
1523
+ setTimeout(() => setIsAnimating(false), 50);
1524
+ }, 200);
1525
+ }, rotationInterval);
1526
+ return () => {
1527
+ if (timerRef.current) clearInterval(timerRef.current);
1528
+ };
1529
+ }, [campaigns.length, rotationInterval]);
1530
+ const handleClick = react.useCallback(
1531
+ (campaign2) => {
1532
+ recordClick(campaign2);
1533
+ const targetUrl = campaign2.customCtaUrl || campaign2.product.url;
1534
+ window.open(targetUrl, "_blank");
1535
+ },
1536
+ [recordClick]
1537
+ );
1538
+ const goToPrev = react.useCallback(() => {
1539
+ if (timerRef.current) clearInterval(timerRef.current);
1540
+ setIsAnimating(true);
1541
+ setTimeout(() => {
1542
+ setCurrentIndex((prev) => (prev - 1 + campaigns.length) % campaigns.length);
1543
+ setTimeout(() => setIsAnimating(false), 50);
1544
+ }, 200);
1545
+ }, [campaigns.length]);
1546
+ const goToNext = react.useCallback(() => {
1547
+ if (timerRef.current) clearInterval(timerRef.current);
1548
+ setIsAnimating(true);
1549
+ setTimeout(() => {
1550
+ setCurrentIndex((prev) => (prev + 1) % campaigns.length);
1551
+ setTimeout(() => setIsAnimating(false), 50);
1552
+ }, 200);
1553
+ }, [campaigns.length]);
1554
+ if (loading || campaigns.length === 0 || isDismissed) return null;
1555
+ const campaign = campaigns[currentIndex];
1556
+ if (!campaign) return null;
1557
+ const { product } = campaign;
1558
+ const displayTitle = campaign.customTitle || product.name;
1559
+ const displayTagline = campaign.customTagline || product.tagline;
1560
+ const displayCta = campaign.customCta || "Learn More";
1561
+ const displayColor = campaign.customProductColor || product.color || "#3B82F6";
1562
+ const displayIcon = campaign.customIcon || product.icon64 || "";
1563
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1564
+ themes.Box,
1565
+ {
1566
+ className,
1567
+ style: {
1568
+ background: `linear-gradient(90deg, ${displayColor}10 0%, ${displayColor}18 50%, ${displayColor}10 100%)`,
1569
+ borderBottom: `2px solid ${displayColor}40`,
1570
+ position: "relative",
1571
+ maxHeight: 100,
1572
+ overflow: "hidden",
1573
+ ...style
1574
+ },
1575
+ children: [
1576
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { style: { height: 2, background: displayColor } }),
1577
+ /* @__PURE__ */ jsxRuntime.jsxs(
1578
+ themes.Flex,
1579
+ {
1580
+ align: "center",
1581
+ justify: "between",
1582
+ gap: "3",
1583
+ px: { initial: "3", sm: "4" },
1584
+ py: "2",
1585
+ style: {
1586
+ opacity: isAnimating ? 0 : 1,
1587
+ transform: isAnimating ? "translateY(-5px)" : "translateY(0)",
1588
+ transition: "all 0.2s ease-out",
1589
+ minHeight: 50,
1590
+ maxHeight: 70
1591
+ },
1592
+ children: [
1593
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Flex, { gap: "1", display: { initial: "none", sm: "flex" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1594
+ themes.IconButton,
1595
+ {
1596
+ size: "1",
1597
+ variant: "ghost",
1598
+ color: "gray",
1599
+ onClick: goToPrev,
1600
+ style: { cursor: "pointer" },
1601
+ "aria-label": "Previous ad",
1602
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { size: 16 })
1603
+ }
1604
+ ) }),
1605
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { align: "center", gap: "3", style: { flex: 1, minWidth: 0 }, children: [
1606
+ /* @__PURE__ */ jsxRuntime.jsx(
1607
+ themes.Box,
1608
+ {
1609
+ style: {
1610
+ width: 40,
1611
+ height: 40,
1612
+ borderRadius: 8,
1613
+ background: `${displayColor}20`,
1614
+ border: `1px solid ${displayColor}40`,
1615
+ display: "flex",
1616
+ alignItems: "center",
1617
+ justifyContent: "center",
1618
+ flexShrink: 0
1619
+ },
1620
+ dangerouslySetInnerHTML: { __html: displayIcon }
1621
+ }
1622
+ ),
1623
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { direction: "column", gap: "0", style: { minWidth: 0, flex: 1 }, children: [
1624
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Text, { size: "2", weight: "bold", style: { lineHeight: 1.2 }, children: displayTitle }),
1625
+ /* @__PURE__ */ jsxRuntime.jsx(
1626
+ themes.Text,
1627
+ {
1628
+ size: "1",
1629
+ color: "gray",
1630
+ style: {
1631
+ display: "-webkit-box",
1632
+ WebkitLineClamp: 1,
1633
+ WebkitBoxOrient: "vertical",
1634
+ overflow: "hidden"
1635
+ },
1636
+ children: displayTagline
1637
+ }
1638
+ )
1639
+ ] }),
1640
+ /* @__PURE__ */ jsxRuntime.jsxs(
1641
+ themes.Button,
1642
+ {
1643
+ size: "1",
1644
+ onClick: () => handleClick(campaign),
1645
+ style: {
1646
+ background: displayColor,
1647
+ color: "white",
1648
+ fontWeight: 600,
1649
+ fontSize: "12px",
1650
+ padding: "0 12px",
1651
+ height: 28,
1652
+ flexShrink: 0
1653
+ },
1654
+ children: [
1655
+ displayCta,
1656
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { size: 12, style: { marginLeft: 4 } })
1657
+ ]
1658
+ }
1659
+ )
1660
+ ] }),
1661
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { gap: "1", align: "center", children: [
1662
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { display: { initial: "none", sm: "block" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1663
+ themes.IconButton,
1664
+ {
1665
+ size: "1",
1666
+ variant: "ghost",
1667
+ color: "gray",
1668
+ onClick: goToNext,
1669
+ style: { cursor: "pointer" },
1670
+ "aria-label": "Next ad",
1671
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { size: 16 })
1672
+ }
1673
+ ) }),
1674
+ /* @__PURE__ */ jsxRuntime.jsx(
1675
+ themes.IconButton,
1676
+ {
1677
+ size: "1",
1678
+ variant: "ghost",
1679
+ color: "gray",
1680
+ onClick: handleDismiss,
1681
+ style: { cursor: "pointer" },
1682
+ "aria-label": "Close banner",
1683
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16 })
1684
+ }
1685
+ )
1686
+ ] })
1687
+ ]
1688
+ }
1689
+ ),
1690
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Flex, { justify: "center", gap: "1", pb: "1", display: { initial: "flex", sm: "none" }, children: campaigns.map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(
1691
+ themes.Box,
1692
+ {
1693
+ style: {
1694
+ width: 6,
1695
+ height: 6,
1696
+ borderRadius: "50%",
1697
+ background: i === currentIndex ? displayColor : "var(--gray-a5)",
1698
+ transition: "background 0.2s ease"
1699
+ }
1700
+ },
1701
+ i
1702
+ )) })
1703
+ ]
1704
+ }
1705
+ );
1706
+ }
1707
+ function AdCarousel({
1708
+ placement = "home_banner",
1709
+ rotationInterval = 2e4,
1710
+ maxCampaigns = 5,
1711
+ className,
1712
+ style
1713
+ }) {
1714
+ const [currentIndex, setCurrentIndex] = react.useState(0);
1715
+ const [isAnimating, setIsAnimating] = react.useState(false);
1716
+ const [progress, setProgress] = react.useState(0);
1717
+ const trackedImpressions = react.useRef(/* @__PURE__ */ new Set());
1718
+ const timerRef = react.useRef(null);
1719
+ const {
1720
+ campaigns,
1721
+ loading,
1722
+ recordImpression,
1723
+ recordClick
1724
+ } = useCommonFeatures.useCampaigns({ placement, maxCampaigns });
1725
+ react.useEffect(() => {
1726
+ if (campaigns.length === 0) return;
1727
+ const campaign2 = campaigns[currentIndex];
1728
+ if (!campaign2 || trackedImpressions.current.has(campaign2.id)) return;
1729
+ trackedImpressions.current.add(campaign2.id);
1730
+ recordImpression(campaign2);
1731
+ }, [currentIndex, campaigns, recordImpression]);
1732
+ react.useEffect(() => {
1733
+ if (campaigns.length <= 1) return;
1734
+ setProgress(0);
1735
+ const progressInterval = 50;
1736
+ const steps = rotationInterval / progressInterval;
1737
+ let currentStep = 0;
1738
+ timerRef.current = setInterval(() => {
1739
+ currentStep++;
1740
+ setProgress(currentStep / steps * 100);
1741
+ if (currentStep >= steps) {
1742
+ setIsAnimating(true);
1743
+ setTimeout(() => {
1744
+ setCurrentIndex((prev) => (prev + 1) % campaigns.length);
1745
+ setProgress(0);
1746
+ currentStep = 0;
1747
+ setTimeout(() => setIsAnimating(false), 50);
1748
+ }, 200);
1749
+ }
1750
+ }, progressInterval);
1751
+ return () => {
1752
+ if (timerRef.current) clearInterval(timerRef.current);
1753
+ };
1754
+ }, [campaigns.length, rotationInterval, currentIndex]);
1755
+ const handleClick = react.useCallback(
1756
+ (campaign2) => {
1757
+ recordClick(campaign2);
1758
+ const targetUrl = campaign2.customCtaUrl || campaign2.product.url;
1759
+ window.open(targetUrl, "_blank");
1760
+ },
1761
+ [recordClick]
1762
+ );
1763
+ const resetTimer = react.useCallback(() => {
1764
+ if (timerRef.current) clearInterval(timerRef.current);
1765
+ setProgress(0);
1766
+ }, []);
1767
+ const goToPrev = react.useCallback(() => {
1768
+ resetTimer();
1769
+ setIsAnimating(true);
1770
+ setTimeout(() => {
1771
+ setCurrentIndex((prev) => (prev - 1 + campaigns.length) % campaigns.length);
1772
+ setTimeout(() => setIsAnimating(false), 50);
1773
+ }, 200);
1774
+ }, [campaigns.length, resetTimer]);
1775
+ const goToNext = react.useCallback(() => {
1776
+ resetTimer();
1777
+ setIsAnimating(true);
1778
+ setTimeout(() => {
1779
+ setCurrentIndex((prev) => (prev + 1) % campaigns.length);
1780
+ setTimeout(() => setIsAnimating(false), 50);
1781
+ }, 200);
1782
+ }, [campaigns.length, resetTimer]);
1783
+ const goToSlide = react.useCallback((index) => {
1784
+ if (index === currentIndex) return;
1785
+ resetTimer();
1786
+ setIsAnimating(true);
1787
+ setTimeout(() => {
1788
+ setCurrentIndex(index);
1789
+ setTimeout(() => setIsAnimating(false), 50);
1790
+ }, 200);
1791
+ }, [currentIndex, resetTimer]);
1792
+ if (loading || campaigns.length === 0) return null;
1793
+ const campaign = campaigns[currentIndex];
1794
+ if (!campaign) return null;
1795
+ const { product } = campaign;
1796
+ const displayTitle = campaign.customTitle || product.name;
1797
+ const displayTagline = campaign.customTagline || product.tagline;
1798
+ const displayCta = campaign.customCta || "Learn More";
1799
+ const displayColor = campaign.customProductColor || product.color || "#3B82F6";
1800
+ const displayIcon = campaign.customIcon || product.icon64 || "";
1801
+ const displayFeatures = campaign.customFeatures || product.features || [];
1802
+ return /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { className, style: { padding: "16px 0", ...style }, children: /* @__PURE__ */ jsxRuntime.jsxs(
1803
+ themes.Card,
1804
+ {
1805
+ style: {
1806
+ background: `linear-gradient(135deg, ${displayColor}08 0%, ${displayColor}15 100%)`,
1807
+ border: `1px solid ${displayColor}25`,
1808
+ overflow: "hidden"
1809
+ },
1810
+ children: [
1811
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { style: { height: 3, background: "var(--gray-a3)", position: "relative" }, children: /* @__PURE__ */ jsxRuntime.jsx(
1812
+ themes.Box,
1813
+ {
1814
+ style: {
1815
+ position: "absolute",
1816
+ top: 0,
1817
+ left: 0,
1818
+ height: "100%",
1819
+ width: `${progress}%`,
1820
+ background: displayColor,
1821
+ transition: "width 50ms linear"
1822
+ }
1823
+ }
1824
+ ) }),
1825
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Box, { p: { initial: "3", sm: "4" }, children: [
1826
+ /* @__PURE__ */ jsxRuntime.jsxs(
1827
+ themes.Flex,
1828
+ {
1829
+ direction: { initial: "column", sm: "row" },
1830
+ align: "center",
1831
+ justify: "between",
1832
+ gap: "4",
1833
+ style: {
1834
+ opacity: isAnimating ? 0 : 1,
1835
+ transform: isAnimating ? "translateX(-10px)" : "translateX(0)",
1836
+ transition: "all 0.2s ease-out"
1837
+ },
1838
+ children: [
1839
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { display: { initial: "none", sm: "block" }, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1840
+ themes.IconButton,
1841
+ {
1842
+ size: "2",
1843
+ variant: "ghost",
1844
+ color: "gray",
1845
+ onClick: goToPrev,
1846
+ style: { cursor: "pointer" },
1847
+ "aria-label": "Previous",
1848
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { size: 20 })
1849
+ }
1850
+ ) }),
1851
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { align: "center", gap: "4", style: { flex: 1, minWidth: 0 }, children: [
1852
+ /* @__PURE__ */ jsxRuntime.jsx(
1853
+ themes.Box,
1854
+ {
1855
+ style: {
1856
+ width: 64,
1857
+ height: 64,
1858
+ borderRadius: 12,
1859
+ background: `${displayColor}18`,
1860
+ border: `2px solid ${displayColor}35`,
1861
+ display: "flex",
1862
+ alignItems: "center",
1863
+ justifyContent: "center",
1864
+ flexShrink: 0
1865
+ },
1866
+ dangerouslySetInnerHTML: { __html: displayIcon }
1867
+ }
1868
+ ),
1869
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { direction: "column", gap: "1", style: { flex: 1, minWidth: 0 }, children: [
1870
+ /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { align: "center", gap: "2", wrap: "wrap", children: [
1871
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Text, { size: "4", weight: "bold", children: displayTitle }),
1872
+ /* @__PURE__ */ jsxRuntime.jsx(
1873
+ themes.Badge,
1874
+ {
1875
+ size: "1",
1876
+ style: {
1877
+ background: `${displayColor}20`,
1878
+ color: displayColor,
1879
+ fontWeight: 600
1880
+ },
1881
+ children: product.type === "extension" ? "Extension" : product.type === "android" ? "App" : "Web"
1882
+ }
1883
+ )
1884
+ ] }),
1885
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Text, { size: "2", color: "gray", style: { lineHeight: 1.4 }, children: displayTagline }),
1886
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Flex, { gap: "3", mt: "1", wrap: "wrap", display: { initial: "none", md: "flex" }, children: displayFeatures.slice(0, 3).map((feature, i) => /* @__PURE__ */ jsxRuntime.jsxs(themes.Flex, { align: "center", gap: "1", children: [
1887
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14, color: displayColor, strokeWidth: 2.5 }),
1888
+ /* @__PURE__ */ jsxRuntime.jsx(themes.Text, { size: "1", color: "gray", children: feature })
1889
+ ] }, i)) })
1890
+ ] }),
1891
+ /* @__PURE__ */ jsxRuntime.jsxs(
1892
+ themes.Button,
1893
+ {
1894
+ size: { initial: "2", sm: "3" },
1895
+ onClick: () => handleClick(campaign),
1896
+ style: {
1897
+ background: displayColor,
1898
+ color: "white",
1899
+ fontWeight: 600,
1900
+ flexShrink: 0,
1901
+ boxShadow: `0 2px 8px ${displayColor}40`
1902
+ },
1903
+ children: [
1904
+ displayCta,
1905
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { size: 16, style: { marginLeft: 6 } })
1906
+ ]
1907
+ }
1908
+ )
1909
+ ] }),
1910
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Box, { display: { initial: "none", sm: "block" }, style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1911
+ themes.IconButton,
1912
+ {
1913
+ size: "2",
1914
+ variant: "ghost",
1915
+ color: "gray",
1916
+ onClick: goToNext,
1917
+ style: { cursor: "pointer" },
1918
+ "aria-label": "Next",
1919
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { size: 20 })
1920
+ }
1921
+ ) })
1922
+ ]
1923
+ }
1924
+ ),
1925
+ campaigns.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(themes.Flex, { justify: "center", gap: "2", mt: "3", children: campaigns.map((c, i) => /* @__PURE__ */ jsxRuntime.jsx(
1926
+ themes.Box,
1927
+ {
1928
+ onClick: () => goToSlide(i),
1929
+ style: {
1930
+ width: 32,
1931
+ height: 4,
1932
+ borderRadius: 2,
1933
+ background: i === currentIndex ? c.product?.color || displayColor : "var(--gray-a4)",
1934
+ cursor: "pointer",
1935
+ transition: "background 0.2s ease"
1936
+ }
1937
+ },
1938
+ i
1939
+ )) })
1940
+ ] })
1941
+ ]
1942
+ }
1943
+ ) });
1944
+ }
1411
1945
  const TYPE_STYLES = {
1412
1946
  info: {
1413
1947
  icon: lucideReact.Info,
@@ -2018,6 +2552,7 @@ function FooterSection({ showContact = true, showSocialLinks = true, showAddress
2018
2552
  ] });
2019
2553
  }
2020
2554
  exports.AdBanner = AdBanner;
2555
+ exports.AdCarousel = AdCarousel;
2021
2556
  exports.AdModal = AdModal;
2022
2557
  exports.AdPanel = AdPanel;
2023
2558
  exports.AdSlider = AdSlider;
@@ -2044,7 +2579,8 @@ exports.SocialLinksBar = SocialLinksBar;
2044
2579
  exports.TaglineVariant = TaglineVariant;
2045
2580
  exports.TestimonialVariant = TestimonialVariant;
2046
2581
  exports.TestimonialsGrid = TestimonialsGrid;
2582
+ exports.TopbarAdBanner = TopbarAdBanner;
2047
2583
  exports.VideoVariant = VideoVariant;
2048
2584
  exports.getLargePanelVariant = getLargePanelVariant;
2049
2585
  exports.getSmallPanelVariant = getSmallPanelVariant;
2050
- //# sourceMappingURL=index-DxjbpnFC.cjs.map
2586
+ //# sourceMappingURL=index--8iNqKw6.cjs.map