@neowhale/storefront 0.2.43 → 0.2.45

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.
@@ -2303,30 +2303,41 @@ function CollageHeroSection({ section, theme, tracking, onEvent }) {
2303
2303
  if (images.length === 0) return null;
2304
2304
  c.overlay_opacity ?? 0.45;
2305
2305
  const count = Math.min(images.length, 5);
2306
+ const imgStyle = {
2307
+ width: "100%",
2308
+ height: "100%",
2309
+ objectFit: "cover",
2310
+ objectPosition: "center top",
2311
+ display: "block"
2312
+ };
2306
2313
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: "100%", overflow: "hidden", position: "relative" }, children: [
2307
- /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2314
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2308
2315
  display: "grid",
2309
- gridTemplateColumns: count >= 4 ? "repeat(4, 1fr)" : "repeat(2, 1fr)",
2310
- gridTemplateRows: count >= 4 ? "minmax(0, 28vh) minmax(0, 28vh)" : "minmax(0, 50vh)",
2311
- gap: "2px"
2312
- }, children: images.slice(0, count).map((img, i) => /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2313
- gridColumn: i === 0 && count >= 4 ? "span 2" : void 0,
2314
- gridRow: i === 0 && count >= 4 ? "span 2" : void 0,
2315
- overflow: "hidden",
2316
- background: theme.surface
2317
- }, children: /* @__PURE__ */ jsxRuntime.jsx(
2318
- "img",
2319
- {
2320
- src: img.url,
2321
- alt: img.alt || "",
2322
- loading: i < 3 ? "eager" : "lazy",
2323
- style: { width: "100%", height: "100%", objectFit: "cover", display: "block" }
2324
- }
2325
- ) }, i)) }),
2316
+ gridTemplateColumns: count >= 3 ? "3fr 2fr" : "1fr",
2317
+ gridTemplateRows: count >= 3 ? "1fr 1fr" : "auto",
2318
+ gap: "2px",
2319
+ height: "100vh",
2320
+ maxHeight: "900px"
2321
+ }, children: [
2322
+ count > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2323
+ gridRow: count >= 3 ? "1 / -1" : void 0,
2324
+ overflow: "hidden",
2325
+ background: theme.surface
2326
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: images[0].url, alt: images[0].alt || "", loading: "eager", style: imgStyle }) }),
2327
+ count >= 2 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "hidden", background: theme.surface }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: images[1].url, alt: images[1].alt || "", loading: "eager", style: imgStyle }) }),
2328
+ count >= 3 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2329
+ overflow: "hidden",
2330
+ background: theme.surface,
2331
+ ...count >= 4 ? { display: "grid", gridTemplateColumns: count >= 5 ? "1fr 1fr" : count >= 4 ? "1fr 1fr" : "1fr", gap: "2px" } : {}
2332
+ }, children: count < 4 ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: images[2].url, alt: images[2].alt || "", loading: "lazy", style: imgStyle }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2333
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: images[2].url, alt: images[2].alt || "", loading: "lazy", style: imgStyle }) }),
2334
+ count >= 4 && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: images[3].url, alt: images[3].alt || "", loading: "lazy", style: imgStyle }) })
2335
+ ] }) })
2336
+ ] }),
2326
2337
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2327
2338
  position: "absolute",
2328
2339
  inset: 0,
2329
- background: "linear-gradient(to top, rgba(0,0,0,0.7) 0%, rgba(0,0,0,0.2) 40%, transparent 60%)",
2340
+ background: "linear-gradient(to top, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.15) 35%, transparent 55%)",
2330
2341
  pointerEvents: "none"
2331
2342
  } }),
2332
2343
  /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
@@ -2780,6 +2791,300 @@ function SuccessState({ theme, heading, message, couponCode }) {
2780
2791
  }, children: couponCode })
2781
2792
  ] }) });
2782
2793
  }
2794
+ function TestimonialsSection({ section, theme }) {
2795
+ const c = section.content;
2796
+ const layout = section.config?.layout || "grid";
2797
+ const reviews = c.reviews || [];
2798
+ if (reviews.length === 0) return null;
2799
+ const overallRating = c.rating ?? 5;
2800
+ const reviewCount = c.review_count ?? reviews.length;
2801
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "3rem 1.5rem", maxWidth: 640, margin: "0 auto" }, children: [
2802
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { textAlign: "center", marginBottom: "2rem" }, children: [
2803
+ c.heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2804
+ fontSize: "clamp(1.25rem, 4vw, 1.75rem)",
2805
+ fontWeight: 300,
2806
+ fontFamily: theme.fontDisplay || "inherit",
2807
+ margin: "0 0 0.75rem",
2808
+ lineHeight: 1.2,
2809
+ letterSpacing: "-0.02em",
2810
+ color: theme.fg
2811
+ }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: c.heading }) }),
2812
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: "0.5rem", marginBottom: "0.25rem" }, children: [
2813
+ /* @__PURE__ */ jsxRuntime.jsx(Stars, { rating: overallRating, color: theme.accent, size: 18 }),
2814
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "1.1rem", fontWeight: 500, color: theme.fg }, children: /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: overallRating.toFixed(1) }) })
2815
+ ] }),
2816
+ c.subtitle ? /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.8rem", color: theme.muted, margin: 0, letterSpacing: "0.1em", textTransform: "uppercase" }, children: c.subtitle }) : /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { fontSize: "0.8rem", color: theme.muted, margin: 0, letterSpacing: "0.1em", textTransform: "uppercase" }, children: [
2817
+ "from ",
2818
+ /* @__PURE__ */ jsxRuntime.jsx(AnimatedText, { text: String(reviewCount) }),
2819
+ " reviews"
2820
+ ] })
2821
+ ] }),
2822
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2823
+ display: "grid",
2824
+ gridTemplateColumns: layout === "list" ? "1fr" : `repeat(${Math.min(reviews.length, 2)}, 1fr)`,
2825
+ gap: "0.75rem"
2826
+ }, children: reviews.map((review, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2827
+ background: theme.surface,
2828
+ border: `1px solid ${theme.fg}08`,
2829
+ padding: "1.25rem"
2830
+ }, children: [
2831
+ /* @__PURE__ */ jsxRuntime.jsx(Stars, { rating: review.rating ?? 5, color: theme.accent, size: 14 }),
2832
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: {
2833
+ fontSize: "0.88rem",
2834
+ color: `${theme.fg}CC`,
2835
+ margin: "0.75rem 0",
2836
+ lineHeight: 1.6,
2837
+ fontWeight: 300,
2838
+ fontStyle: "italic"
2839
+ }, children: [
2840
+ '"',
2841
+ review.text,
2842
+ '"'
2843
+ ] }),
2844
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "baseline", gap: "0.375rem" }, children: [
2845
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.8rem", fontWeight: 500, color: theme.fg }, children: review.name }),
2846
+ review.location && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.7rem", color: theme.muted }, children: review.location })
2847
+ ] })
2848
+ ] }, i)) })
2849
+ ] });
2850
+ }
2851
+ function Stars({ rating, color, size }) {
2852
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "inline-flex", gap: "1px" }, children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsxRuntime.jsx(
2853
+ "svg",
2854
+ {
2855
+ width: size,
2856
+ height: size,
2857
+ viewBox: "0 0 20 20",
2858
+ fill: n <= Math.round(rating) ? color : "none",
2859
+ stroke: color,
2860
+ strokeWidth: 1.5,
2861
+ children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 1.5l2.47 5.01 5.53.8-4 3.9.94 5.5L10 14.26 5.06 16.7l.94-5.5-4-3.9 5.53-.8z" })
2862
+ },
2863
+ n
2864
+ )) });
2865
+ }
2866
+ function ValueStackSection({ section, theme }) {
2867
+ const c = section.content;
2868
+ const items = c.items || [];
2869
+ if (items.length === 0) return null;
2870
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "3rem 1.5rem", maxWidth: 520, margin: "0 auto" }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { background: theme.surface, border: `1px solid ${theme.fg}08`, padding: "clamp(1.5rem, 5vw, 2.5rem)" }, children: [
2871
+ c.heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2872
+ fontSize: "clamp(1.25rem, 4vw, 1.5rem)",
2873
+ fontWeight: 300,
2874
+ fontFamily: theme.fontDisplay || "inherit",
2875
+ margin: "0 0 0.25rem",
2876
+ lineHeight: 1.2,
2877
+ letterSpacing: "-0.02em",
2878
+ color: theme.fg,
2879
+ textAlign: "center"
2880
+ }, children: c.heading }),
2881
+ c.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
2882
+ fontSize: "0.8rem",
2883
+ color: theme.accent,
2884
+ margin: "0 0 1.5rem",
2885
+ letterSpacing: "0.15em",
2886
+ textTransform: "uppercase",
2887
+ textAlign: "center"
2888
+ }, children: c.subtitle }),
2889
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "0.625rem" }, children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2890
+ display: "flex",
2891
+ alignItems: "flex-start",
2892
+ gap: "0.75rem",
2893
+ padding: "0.5rem 0",
2894
+ borderBottom: i < items.length - 1 ? `1px solid ${theme.fg}08` : void 0
2895
+ }, children: [
2896
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { color: theme.accent, fontSize: "1rem", lineHeight: 1, flexShrink: 0, marginTop: "0.1rem" }, children: "\u2713" }),
2897
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { flex: 1, fontSize: "0.9rem", color: `${theme.fg}CC`, fontWeight: 300, lineHeight: 1.5 }, children: item.text }),
2898
+ item.value && /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2899
+ fontSize: "0.8rem",
2900
+ color: theme.muted,
2901
+ fontWeight: 400,
2902
+ textDecoration: "line-through",
2903
+ flexShrink: 0
2904
+ }, children: item.value })
2905
+ ] }, i)) }),
2906
+ (c.total_value || c.offer_label) && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
2907
+ marginTop: "1.5rem",
2908
+ padding: "1rem",
2909
+ background: `${theme.accent}0A`,
2910
+ border: `1px dashed ${theme.accent}30`,
2911
+ textAlign: "center"
2912
+ }, children: [
2913
+ c.total_value && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { fontSize: "0.75rem", color: theme.muted, marginBottom: "0.25rem", letterSpacing: "0.1em", textTransform: "uppercase" }, children: [
2914
+ "Total value: ",
2915
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { textDecoration: "line-through" }, children: c.total_value })
2916
+ ] }),
2917
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2918
+ fontSize: "clamp(1.25rem, 4vw, 1.5rem)",
2919
+ fontWeight: 500,
2920
+ fontFamily: theme.fontDisplay || "inherit",
2921
+ color: theme.accent
2922
+ }, children: c.offer_label || "Free" })
2923
+ ] })
2924
+ ] }) });
2925
+ }
2926
+ function FAQSection({ section, theme }) {
2927
+ const c = section.content;
2928
+ const items = c.items || [];
2929
+ if (items.length === 0) return null;
2930
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2.5rem 1.5rem", maxWidth: 580, margin: "0 auto" }, children: [
2931
+ c.heading && /* @__PURE__ */ jsxRuntime.jsx("h2", { style: {
2932
+ fontSize: 11,
2933
+ fontWeight: 500,
2934
+ textTransform: "uppercase",
2935
+ letterSpacing: "0.25em",
2936
+ color: `${theme.fg}40`,
2937
+ margin: "0 0 1.25rem",
2938
+ textAlign: "center"
2939
+ }, children: c.heading }),
2940
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: 0 }, children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsx(FAQItem, { question: item.question, answer: item.answer, theme }, i)) })
2941
+ ] });
2942
+ }
2943
+ function FAQItem({ question, answer, theme }) {
2944
+ const [open, setOpen] = react.useState(false);
2945
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { borderBottom: `1px solid ${theme.fg}0A` }, children: [
2946
+ /* @__PURE__ */ jsxRuntime.jsxs(
2947
+ "button",
2948
+ {
2949
+ onClick: () => setOpen(!open),
2950
+ style: {
2951
+ width: "100%",
2952
+ display: "flex",
2953
+ justifyContent: "space-between",
2954
+ alignItems: "center",
2955
+ padding: "1rem 0",
2956
+ background: "none",
2957
+ border: "none",
2958
+ cursor: "pointer",
2959
+ color: theme.fg,
2960
+ fontFamily: "inherit",
2961
+ textAlign: "left"
2962
+ },
2963
+ children: [
2964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.9rem", fontWeight: 400, lineHeight: 1.4 }, children: question }),
2965
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
2966
+ fontSize: "1.25rem",
2967
+ fontWeight: 200,
2968
+ color: theme.muted,
2969
+ transition: "transform 0.2s",
2970
+ transform: open ? "rotate(45deg)" : "none",
2971
+ flexShrink: 0,
2972
+ marginLeft: "1rem"
2973
+ }, children: "+" })
2974
+ ]
2975
+ }
2976
+ ),
2977
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2978
+ maxHeight: open ? "20rem" : 0,
2979
+ overflow: "hidden",
2980
+ transition: "max-height 0.3s ease"
2981
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
2982
+ fontSize: "0.85rem",
2983
+ color: `${theme.fg}88`,
2984
+ lineHeight: 1.7,
2985
+ fontWeight: 300,
2986
+ margin: 0,
2987
+ padding: "0 0 1rem"
2988
+ }, children: answer }) })
2989
+ ] });
2990
+ }
2991
+ function TrustBadgesSection({ section, theme }) {
2992
+ const c = section.content;
2993
+ const badges = c.badges || [];
2994
+ if (badges.length === 0) return null;
2995
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
2996
+ padding: "1.25rem 1.5rem",
2997
+ borderTop: `1px solid ${theme.fg}08`,
2998
+ borderBottom: `1px solid ${theme.fg}08`
2999
+ }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3000
+ display: "flex",
3001
+ justifyContent: "center",
3002
+ alignItems: "center",
3003
+ gap: "clamp(1rem, 4vw, 2.5rem)",
3004
+ flexWrap: "wrap"
3005
+ }, children: badges.map((badge, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
3006
+ display: "flex",
3007
+ alignItems: "center",
3008
+ gap: "0.375rem"
3009
+ }, children: [
3010
+ badge.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "0.9rem" }, children: badge.icon }),
3011
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: {
3012
+ fontSize: "0.7rem",
3013
+ fontWeight: 500,
3014
+ textTransform: "uppercase",
3015
+ letterSpacing: "0.15em",
3016
+ color: theme.muted
3017
+ }, children: badge.label })
3018
+ ] }, i)) }) });
3019
+ }
3020
+ function CountdownSection({ section, theme }) {
3021
+ const c = section.content;
3022
+ const endDate = c.end_date ? new Date(c.end_date) : null;
3023
+ const [remaining, setRemaining] = react.useState(null);
3024
+ const [expired, setExpired] = react.useState(false);
3025
+ react.useEffect(() => {
3026
+ if (!endDate) return;
3027
+ function tick() {
3028
+ const diff = endDate.getTime() - Date.now();
3029
+ if (diff <= 0) {
3030
+ setExpired(true);
3031
+ return;
3032
+ }
3033
+ setRemaining({
3034
+ d: Math.floor(diff / 864e5),
3035
+ h: Math.floor(diff % 864e5 / 36e5),
3036
+ m: Math.floor(diff % 36e5 / 6e4),
3037
+ s: Math.floor(diff % 6e4 / 1e3)
3038
+ });
3039
+ }
3040
+ tick();
3041
+ const id = setInterval(tick, 1e3);
3042
+ return () => clearInterval(id);
3043
+ }, [endDate?.getTime()]);
3044
+ if (!endDate) return null;
3045
+ if (expired) {
3046
+ return c.expired_text ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { padding: "1.5rem", textAlign: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.85rem", color: theme.muted, margin: 0 }, children: c.expired_text }) }) : null;
3047
+ }
3048
+ if (!remaining) return null;
3049
+ const units = [
3050
+ { label: "Days", value: remaining.d },
3051
+ { label: "Hours", value: remaining.h },
3052
+ { label: "Min", value: remaining.m },
3053
+ { label: "Sec", value: remaining.s }
3054
+ ];
3055
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: "2rem 1.5rem", maxWidth: 480, margin: "0 auto", textAlign: "center" }, children: [
3056
+ c.heading && /* @__PURE__ */ jsxRuntime.jsx("p", { style: {
3057
+ fontSize: "0.8rem",
3058
+ color: theme.accent,
3059
+ margin: "0 0 1rem",
3060
+ letterSpacing: "0.15em",
3061
+ textTransform: "uppercase"
3062
+ }, children: c.heading }),
3063
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "center", gap: "0.5rem" }, children: units.map((u) => /* @__PURE__ */ jsxRuntime.jsxs("div", { style: {
3064
+ minWidth: "clamp(3.5rem, 12vw, 4.5rem)",
3065
+ padding: "0.75rem 0.25rem",
3066
+ background: theme.surface,
3067
+ border: `1px solid ${theme.fg}08`
3068
+ }, children: [
3069
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3070
+ fontFamily: theme.fontDisplay || "inherit",
3071
+ fontSize: "clamp(1.5rem, 5vw, 2rem)",
3072
+ fontWeight: 300,
3073
+ lineHeight: 1,
3074
+ color: theme.fg,
3075
+ fontVariantNumeric: "tabular-nums"
3076
+ }, children: String(u.value).padStart(2, "0") }),
3077
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: {
3078
+ fontSize: "0.6rem",
3079
+ fontWeight: 500,
3080
+ textTransform: "uppercase",
3081
+ letterSpacing: "0.2em",
3082
+ color: theme.muted,
3083
+ marginTop: "0.375rem"
3084
+ }, children: u.label })
3085
+ ] }, u.label)) })
3086
+ ] });
3087
+ }
2783
3088
  function SectionRenderer({
2784
3089
  section,
2785
3090
  data,
@@ -2814,6 +3119,16 @@ function SectionRenderer({
2814
3119
  return /* @__PURE__ */ jsxRuntime.jsx(SocialLinksSection, { section, theme });
2815
3120
  case "lead_capture":
2816
3121
  return /* @__PURE__ */ jsxRuntime.jsx(LeadCaptureSection, { section, data, theme, onEvent });
3122
+ case "testimonials":
3123
+ return /* @__PURE__ */ jsxRuntime.jsx(TestimonialsSection, { section, theme });
3124
+ case "value_stack":
3125
+ return /* @__PURE__ */ jsxRuntime.jsx(ValueStackSection, { section, theme });
3126
+ case "faq":
3127
+ return /* @__PURE__ */ jsxRuntime.jsx(FAQSection, { section, theme });
3128
+ case "trust_badges":
3129
+ return /* @__PURE__ */ jsxRuntime.jsx(TrustBadgesSection, { section, theme });
3130
+ case "countdown":
3131
+ return /* @__PURE__ */ jsxRuntime.jsx(CountdownSection, { section, theme });
2817
3132
  case "divider":
2818
3133
  return /* @__PURE__ */ jsxRuntime.jsx(DividerSection, { theme });
2819
3134
  default: