@opensite/ui 3.12.2 → 3.14.0

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.
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  'use strict';
3
3
 
4
- var React = require('react');
4
+ var React4 = require('react');
5
5
  var clsx = require('clsx');
6
6
  var tailwindMerge = require('tailwind-merge');
7
7
  var icon = require('@page-speed/icon');
@@ -29,14 +29,14 @@ function _interopNamespace(e) {
29
29
  return Object.freeze(n);
30
30
  }
31
31
 
32
- var React__namespace = /*#__PURE__*/_interopNamespace(React);
32
+ var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
33
33
 
34
34
  // components/blocks/hero/hero-event-registration.tsx
35
35
  function cn(...inputs) {
36
36
  return tailwindMerge.twMerge(clsx.clsx(inputs));
37
37
  }
38
38
  var DEFAULT_ICON_API_KEY = "au382bi7fsh96w9h9xlrnat2jglx";
39
- var DynamicIcon = React__namespace.memo(function DynamicIcon2({
39
+ var DynamicIcon = React4__namespace.memo(function DynamicIcon2({
40
40
  apiKey,
41
41
  name,
42
42
  ...props
@@ -89,7 +89,7 @@ var maxWidthStyles = {
89
89
  "4xl": "max-w-[1536px]",
90
90
  full: "max-w-full"
91
91
  };
92
- var Container = React__namespace.default.forwardRef(
92
+ var Container = React4__namespace.default.forwardRef(
93
93
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
94
94
  const Component = as;
95
95
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -395,7 +395,7 @@ var spacingStyles = {
395
395
  };
396
396
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl", "hero"];
397
397
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
398
- var Section = React__namespace.default.forwardRef(
398
+ var Section = React4__namespace.default.forwardRef(
399
399
  ({
400
400
  id,
401
401
  title,
@@ -610,14 +610,15 @@ function HeroEventRegistration({
610
610
  logoClassName,
611
611
  directionConfig = { desktop: "mediaRight", mobile: "mediaTop" }
612
612
  }) {
613
- const renderBadge = React.useMemo(() => {
613
+ const renderBadge = React4.useMemo(() => {
614
614
  if (badgeSlot) return badgeSlot;
615
+ if (!badgeText && !badgeIcon) return null;
615
616
  return /* @__PURE__ */ jsxRuntime.jsxs(Badge, { className: "px-4 py-1 gap-2", children: [
616
617
  badgeIcon && /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: badgeIcon, size: 18 }),
617
618
  badgeText
618
619
  ] });
619
620
  }, [badgeSlot, badgeIcon, badgeText]);
620
- const renderStats = React.useMemo(() => {
621
+ const renderStats = React4.useMemo(() => {
621
622
  if (statsSlot) return statsSlot;
622
623
  if (!stats || stats.length === 0) return null;
623
624
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -627,7 +628,7 @@ function HeroEventRegistration({
627
628
  "flex flex-col md:flex-row items-center gap-4 md:gap-6 pt-8 md:pt-12 w-full md:w-fit",
628
629
  statsClassName
629
630
  ),
630
- children: stats.map((stat, index) => /* @__PURE__ */ jsxRuntime.jsxs(React__namespace.Fragment, { children: [
631
+ children: stats.map((stat, index) => /* @__PURE__ */ jsxRuntime.jsxs(React4__namespace.Fragment, { children: [
631
632
  index > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-12 w-px bg-border hidden md:flex" }),
632
633
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 justify-between flex-row md:flex-col text-left md:text-center w-full md:w-fit border md:border-none rounded-xl md:rounded-none p-4 md:p-0", children: [
633
634
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -658,57 +659,67 @@ function HeroEventRegistration({
658
659
  }
659
660
  );
660
661
  }, [statsSlot, stats, statsClassName]);
661
- const renderLocation = React.useMemo(() => {
662
- if (locationSlot) return locationSlot;
663
- if (!locationLabel && !locationSublabel) return null;
664
- return /* @__PURE__ */ jsxRuntime.jsx(
665
- "div",
666
- {
667
- className: cn(
668
- "bg-card text-card-foreground",
669
- "rounded-2xl p-2 md:p-4 shadow-lg",
670
- "absolute bottom-0 translate-y-1/2",
671
- "left-1/2 -translate-x-1/2 w-[90%]",
672
- "md:-left-4 md:translate-x-0 md:w-auto",
673
- "ring-4 ring-primary"
674
- ),
675
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
676
- /* @__PURE__ */ jsxRuntime.jsx(
677
- "div",
678
- {
679
- className: cn(
680
- "flex items-center justify-center shrink-0",
681
- "size-12 rounded-xl bg-primary text-primary-foreground"
682
- ),
683
- children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
684
- }
662
+ const buildLocation = React4__namespace.useCallback(
663
+ (positioned) => {
664
+ if (locationSlot) return locationSlot;
665
+ if (!locationLabel && !locationSublabel) return null;
666
+ return /* @__PURE__ */ jsxRuntime.jsx(
667
+ "div",
668
+ {
669
+ className: cn(
670
+ "bg-card text-card-foreground",
671
+ "rounded-2xl p-2 md:p-4 shadow-lg",
672
+ "ring-4 ring-primary",
673
+ positioned ? cn(
674
+ "absolute bottom-0 translate-y-1/2",
675
+ "left-1/2 -translate-x-1/2 w-[90%]",
676
+ "md:-left-4 md:translate-x-0 md:w-auto"
677
+ ) : "w-full md:w-fit"
685
678
  ),
686
- /* @__PURE__ */ jsxRuntime.jsxs(
687
- "div",
688
- {
689
- className: cn(
690
- "flex flex-col items-start justify-center",
691
- "text-card-foreground gap-0 pr-0 md:pr-2"
692
- ),
693
- children: [
694
- locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
695
- locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
696
- "div",
697
- {
698
- className: cn(
699
- "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
700
- ),
701
- children: locationSublabel
702
- }
703
- ) : locationSublabel)
704
- ]
705
- }
706
- )
707
- ] })
708
- }
709
- );
710
- }, [locationSlot, locationLabel, locationSublabel]);
711
- const renderImage = React.useMemo(() => {
679
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
680
+ /* @__PURE__ */ jsxRuntime.jsx(
681
+ "div",
682
+ {
683
+ className: cn(
684
+ "flex items-center justify-center shrink-0",
685
+ "size-12 rounded-xl bg-primary text-primary-foreground"
686
+ ),
687
+ children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
688
+ }
689
+ ),
690
+ /* @__PURE__ */ jsxRuntime.jsxs(
691
+ "div",
692
+ {
693
+ className: cn(
694
+ "flex flex-col items-start justify-center",
695
+ "text-card-foreground gap-0 pr-0 md:pr-2"
696
+ ),
697
+ children: [
698
+ locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
699
+ locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
700
+ "div",
701
+ {
702
+ className: cn(
703
+ "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
704
+ ),
705
+ children: locationSublabel
706
+ }
707
+ ) : locationSublabel)
708
+ ]
709
+ }
710
+ )
711
+ ] })
712
+ }
713
+ );
714
+ },
715
+ [locationSlot, locationLabel, locationSublabel]
716
+ );
717
+ const renderLocation = React4.useMemo(() => buildLocation(true), [buildLocation]);
718
+ const renderInlineLocation = React4.useMemo(
719
+ () => buildLocation(false),
720
+ [buildLocation]
721
+ );
722
+ const renderImage = React4.useMemo(() => {
712
723
  if (imageSlot) return imageSlot;
713
724
  if (!image) return null;
714
725
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full md:w-[50%]", children: [
@@ -736,6 +747,7 @@ function HeroEventRegistration({
736
747
  }, [imageSlot, image, imageClassName, optixFlowConfig, renderLocation]);
737
748
  const desktopOrder = directionConfig.desktop === "mediaRight" ? "md:flex-row" : "md:flex-row-reverse";
738
749
  const mobileOrder = directionConfig.mobile === "mediaTop" ? "flex-col-reverse" : "flex-col";
750
+ const locationInImage = !imageSlot && Boolean(image);
739
751
  return /* @__PURE__ */ jsxRuntime.jsx(
740
752
  Section,
741
753
  {
@@ -784,6 +796,7 @@ function HeroEventRegistration({
784
796
  }
785
797
  ) : heading),
786
798
  description && (typeof description === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("text-lg text-balance", descriptionClassName), children: description }) : description),
799
+ !locationInImage && renderInlineLocation,
787
800
  /* @__PURE__ */ jsxRuntime.jsx(
788
801
  BlockActions,
789
802
  {
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import * as React from 'react';
3
- import React__default, { useMemo } from 'react';
2
+ import * as React4 from 'react';
3
+ import React4__default, { useMemo } from 'react';
4
4
  import { clsx } from 'clsx';
5
5
  import { twMerge } from 'tailwind-merge';
6
6
  import { Icon } from '@page-speed/icon';
@@ -15,7 +15,7 @@ function cn(...inputs) {
15
15
  return twMerge(clsx(inputs));
16
16
  }
17
17
  var DEFAULT_ICON_API_KEY = "au382bi7fsh96w9h9xlrnat2jglx";
18
- var DynamicIcon = React.memo(function DynamicIcon2({
18
+ var DynamicIcon = React4.memo(function DynamicIcon2({
19
19
  apiKey,
20
20
  name,
21
21
  ...props
@@ -68,7 +68,7 @@ var maxWidthStyles = {
68
68
  "4xl": "max-w-[1536px]",
69
69
  full: "max-w-full"
70
70
  };
71
- var Container = React__default.forwardRef(
71
+ var Container = React4__default.forwardRef(
72
72
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
73
73
  const Component = as;
74
74
  return /* @__PURE__ */ jsx(
@@ -374,7 +374,7 @@ var spacingStyles = {
374
374
  };
375
375
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl", "hero"];
376
376
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
377
- var Section = React__default.forwardRef(
377
+ var Section = React4__default.forwardRef(
378
378
  ({
379
379
  id,
380
380
  title,
@@ -591,6 +591,7 @@ function HeroEventRegistration({
591
591
  }) {
592
592
  const renderBadge = useMemo(() => {
593
593
  if (badgeSlot) return badgeSlot;
594
+ if (!badgeText && !badgeIcon) return null;
594
595
  return /* @__PURE__ */ jsxs(Badge, { className: "px-4 py-1 gap-2", children: [
595
596
  badgeIcon && /* @__PURE__ */ jsx(DynamicIcon, { name: badgeIcon, size: 18 }),
596
597
  badgeText
@@ -606,7 +607,7 @@ function HeroEventRegistration({
606
607
  "flex flex-col md:flex-row items-center gap-4 md:gap-6 pt-8 md:pt-12 w-full md:w-fit",
607
608
  statsClassName
608
609
  ),
609
- children: stats.map((stat, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
610
+ children: stats.map((stat, index) => /* @__PURE__ */ jsxs(React4.Fragment, { children: [
610
611
  index > 0 && /* @__PURE__ */ jsx("div", { className: "h-12 w-px bg-border hidden md:flex" }),
611
612
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 justify-between flex-row md:flex-col text-left md:text-center w-full md:w-fit border md:border-none rounded-xl md:rounded-none p-4 md:p-0", children: [
612
613
  /* @__PURE__ */ jsxs(
@@ -637,56 +638,66 @@ function HeroEventRegistration({
637
638
  }
638
639
  );
639
640
  }, [statsSlot, stats, statsClassName]);
640
- const renderLocation = useMemo(() => {
641
- if (locationSlot) return locationSlot;
642
- if (!locationLabel && !locationSublabel) return null;
643
- return /* @__PURE__ */ jsx(
644
- "div",
645
- {
646
- className: cn(
647
- "bg-card text-card-foreground",
648
- "rounded-2xl p-2 md:p-4 shadow-lg",
649
- "absolute bottom-0 translate-y-1/2",
650
- "left-1/2 -translate-x-1/2 w-[90%]",
651
- "md:-left-4 md:translate-x-0 md:w-auto",
652
- "ring-4 ring-primary"
653
- ),
654
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
655
- /* @__PURE__ */ jsx(
656
- "div",
657
- {
658
- className: cn(
659
- "flex items-center justify-center shrink-0",
660
- "size-12 rounded-xl bg-primary text-primary-foreground"
661
- ),
662
- children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
663
- }
641
+ const buildLocation = React4.useCallback(
642
+ (positioned) => {
643
+ if (locationSlot) return locationSlot;
644
+ if (!locationLabel && !locationSublabel) return null;
645
+ return /* @__PURE__ */ jsx(
646
+ "div",
647
+ {
648
+ className: cn(
649
+ "bg-card text-card-foreground",
650
+ "rounded-2xl p-2 md:p-4 shadow-lg",
651
+ "ring-4 ring-primary",
652
+ positioned ? cn(
653
+ "absolute bottom-0 translate-y-1/2",
654
+ "left-1/2 -translate-x-1/2 w-[90%]",
655
+ "md:-left-4 md:translate-x-0 md:w-auto"
656
+ ) : "w-full md:w-fit"
664
657
  ),
665
- /* @__PURE__ */ jsxs(
666
- "div",
667
- {
668
- className: cn(
669
- "flex flex-col items-start justify-center",
670
- "text-card-foreground gap-0 pr-0 md:pr-2"
671
- ),
672
- children: [
673
- locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
674
- locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsx(
675
- "div",
676
- {
677
- className: cn(
678
- "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
679
- ),
680
- children: locationSublabel
681
- }
682
- ) : locationSublabel)
683
- ]
684
- }
685
- )
686
- ] })
687
- }
688
- );
689
- }, [locationSlot, locationLabel, locationSublabel]);
658
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
659
+ /* @__PURE__ */ jsx(
660
+ "div",
661
+ {
662
+ className: cn(
663
+ "flex items-center justify-center shrink-0",
664
+ "size-12 rounded-xl bg-primary text-primary-foreground"
665
+ ),
666
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
667
+ }
668
+ ),
669
+ /* @__PURE__ */ jsxs(
670
+ "div",
671
+ {
672
+ className: cn(
673
+ "flex flex-col items-start justify-center",
674
+ "text-card-foreground gap-0 pr-0 md:pr-2"
675
+ ),
676
+ children: [
677
+ locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
678
+ locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsx(
679
+ "div",
680
+ {
681
+ className: cn(
682
+ "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
683
+ ),
684
+ children: locationSublabel
685
+ }
686
+ ) : locationSublabel)
687
+ ]
688
+ }
689
+ )
690
+ ] })
691
+ }
692
+ );
693
+ },
694
+ [locationSlot, locationLabel, locationSublabel]
695
+ );
696
+ const renderLocation = useMemo(() => buildLocation(true), [buildLocation]);
697
+ const renderInlineLocation = useMemo(
698
+ () => buildLocation(false),
699
+ [buildLocation]
700
+ );
690
701
  const renderImage = useMemo(() => {
691
702
  if (imageSlot) return imageSlot;
692
703
  if (!image) return null;
@@ -715,6 +726,7 @@ function HeroEventRegistration({
715
726
  }, [imageSlot, image, imageClassName, optixFlowConfig, renderLocation]);
716
727
  const desktopOrder = directionConfig.desktop === "mediaRight" ? "md:flex-row" : "md:flex-row-reverse";
717
728
  const mobileOrder = directionConfig.mobile === "mediaTop" ? "flex-col-reverse" : "flex-col";
729
+ const locationInImage = !imageSlot && Boolean(image);
718
730
  return /* @__PURE__ */ jsx(
719
731
  Section,
720
732
  {
@@ -763,6 +775,7 @@ function HeroEventRegistration({
763
775
  }
764
776
  ) : heading),
765
777
  description && (typeof description === "string" ? /* @__PURE__ */ jsx("p", { className: cn("text-lg text-balance", descriptionClassName), children: description }) : description),
778
+ !locationInImage && renderInlineLocation,
766
779
  /* @__PURE__ */ jsx(
767
780
  BlockActions,
768
781
  {
package/dist/registry.cjs CHANGED
@@ -44444,10 +44444,9 @@ function TestimonialsListVerified({
44444
44444
  ] }),
44445
44445
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-base flex-col items-start", children: [
44446
44446
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: review.author }) : review.author),
44447
- review.verified && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
44447
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
44448
44448
  /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
44449
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
44450
- !verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: "Verified Purchase" })
44449
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
44451
44450
  ] })
44452
44451
  ] })
44453
44452
  ]
@@ -44632,10 +44631,9 @@ function TestimonialsImagesHelpful({
44632
44631
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-0", children: [
44633
44632
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-col md:flex-row", children: [
44634
44633
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: review.author }) : review.author),
44635
- review.verified && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
44634
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
44636
44635
  /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
44637
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
44638
- !verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: "Verified Purchase" })
44636
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
44639
44637
  ] })
44640
44638
  ] }),
44641
44639
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
@@ -45786,7 +45784,13 @@ function TestimonialsGridAddReview({
45786
45784
  const avatarSrc = getAvatarSrc(testimonial);
45787
45785
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
45788
45786
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
45789
- /* @__PURE__ */ jsxRuntime.jsx(StarRating, { rating: 5, size: 20 }),
45787
+ /* @__PURE__ */ jsxRuntime.jsx(
45788
+ StarRating,
45789
+ {
45790
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
45791
+ size: 20
45792
+ }
45793
+ ),
45790
45794
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsxs(
45791
45795
  "p",
45792
45796
  {
@@ -46748,7 +46752,13 @@ function TestimonialsStatsHeader({
46748
46752
  ),
46749
46753
  children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
46750
46754
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
46751
- /* @__PURE__ */ jsxRuntime.jsx(StarRating, { rating: 5, size: 20 }),
46755
+ /* @__PURE__ */ jsxRuntime.jsx(
46756
+ StarRating,
46757
+ {
46758
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
46759
+ size: 20
46760
+ }
46761
+ ),
46752
46762
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsxs(
46753
46763
  "p",
46754
46764
  {
@@ -48325,7 +48335,7 @@ function TestimonialsParallaxNumber({
48325
48335
  ),
48326
48336
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex", children: [
48327
48337
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center border-r-0 md:border-r border-border/30 pr-4 md:pr-16", children: [
48328
- /* @__PURE__ */ jsxRuntime.jsxs(
48338
+ verticalLabel && /* @__PURE__ */ jsxRuntime.jsx(
48329
48339
  framerMotion.motion.span,
48330
48340
  {
48331
48341
  className: "text-sm tracking-widest uppercase",
@@ -48333,10 +48343,7 @@ function TestimonialsParallaxNumber({
48333
48343
  initial: { opacity: 0 },
48334
48344
  animate: { opacity: 1 },
48335
48345
  transition: { delay: 0.3 },
48336
- children: [
48337
- verticalLabel && (typeof verticalLabel === "string" ? verticalLabel : verticalLabel),
48338
- !verticalLabel && "Testimonials"
48339
- ]
48346
+ children: verticalLabel
48340
48347
  }
48341
48348
  ),
48342
48349
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mt-8 h-32 w-4 bg-muted ring-2 ring-primary rounded", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -61957,6 +61964,7 @@ function HeroEventRegistration({
61957
61964
  }) {
61958
61965
  const renderBadge = React30.useMemo(() => {
61959
61966
  if (badgeSlot) return badgeSlot;
61967
+ if (!badgeText && !badgeIcon) return null;
61960
61968
  return /* @__PURE__ */ jsxRuntime.jsxs(Badge, { className: "px-4 py-1 gap-2", children: [
61961
61969
  badgeIcon && /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: badgeIcon, size: 18 }),
61962
61970
  badgeText
@@ -62003,56 +62011,66 @@ function HeroEventRegistration({
62003
62011
  }
62004
62012
  );
62005
62013
  }, [statsSlot, stats, statsClassName]);
62006
- const renderLocation = React30.useMemo(() => {
62007
- if (locationSlot) return locationSlot;
62008
- if (!locationLabel && !locationSublabel) return null;
62009
- return /* @__PURE__ */ jsxRuntime.jsx(
62010
- "div",
62011
- {
62012
- className: cn(
62013
- "bg-card text-card-foreground",
62014
- "rounded-2xl p-2 md:p-4 shadow-lg",
62015
- "absolute bottom-0 translate-y-1/2",
62016
- "left-1/2 -translate-x-1/2 w-[90%]",
62017
- "md:-left-4 md:translate-x-0 md:w-auto",
62018
- "ring-4 ring-primary"
62019
- ),
62020
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
62021
- /* @__PURE__ */ jsxRuntime.jsx(
62022
- "div",
62023
- {
62024
- className: cn(
62025
- "flex items-center justify-center shrink-0",
62026
- "size-12 rounded-xl bg-primary text-primary-foreground"
62027
- ),
62028
- children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
62029
- }
62014
+ const buildLocation = React30__namespace.useCallback(
62015
+ (positioned) => {
62016
+ if (locationSlot) return locationSlot;
62017
+ if (!locationLabel && !locationSublabel) return null;
62018
+ return /* @__PURE__ */ jsxRuntime.jsx(
62019
+ "div",
62020
+ {
62021
+ className: cn(
62022
+ "bg-card text-card-foreground",
62023
+ "rounded-2xl p-2 md:p-4 shadow-lg",
62024
+ "ring-4 ring-primary",
62025
+ positioned ? cn(
62026
+ "absolute bottom-0 translate-y-1/2",
62027
+ "left-1/2 -translate-x-1/2 w-[90%]",
62028
+ "md:-left-4 md:translate-x-0 md:w-auto"
62029
+ ) : "w-full md:w-fit"
62030
62030
  ),
62031
- /* @__PURE__ */ jsxRuntime.jsxs(
62032
- "div",
62033
- {
62034
- className: cn(
62035
- "flex flex-col items-start justify-center",
62036
- "text-card-foreground gap-0 pr-0 md:pr-2"
62037
- ),
62038
- children: [
62039
- locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
62040
- locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
62041
- "div",
62042
- {
62043
- className: cn(
62044
- "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
62045
- ),
62046
- children: locationSublabel
62047
- }
62048
- ) : locationSublabel)
62049
- ]
62050
- }
62051
- )
62052
- ] })
62053
- }
62054
- );
62055
- }, [locationSlot, locationLabel, locationSublabel]);
62031
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
62032
+ /* @__PURE__ */ jsxRuntime.jsx(
62033
+ "div",
62034
+ {
62035
+ className: cn(
62036
+ "flex items-center justify-center shrink-0",
62037
+ "size-12 rounded-xl bg-primary text-primary-foreground"
62038
+ ),
62039
+ children: /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
62040
+ }
62041
+ ),
62042
+ /* @__PURE__ */ jsxRuntime.jsxs(
62043
+ "div",
62044
+ {
62045
+ className: cn(
62046
+ "flex flex-col items-start justify-center",
62047
+ "text-card-foreground gap-0 pr-0 md:pr-2"
62048
+ ),
62049
+ children: [
62050
+ locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
62051
+ locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx(
62052
+ "div",
62053
+ {
62054
+ className: cn(
62055
+ "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
62056
+ ),
62057
+ children: locationSublabel
62058
+ }
62059
+ ) : locationSublabel)
62060
+ ]
62061
+ }
62062
+ )
62063
+ ] })
62064
+ }
62065
+ );
62066
+ },
62067
+ [locationSlot, locationLabel, locationSublabel]
62068
+ );
62069
+ const renderLocation = React30.useMemo(() => buildLocation(true), [buildLocation]);
62070
+ const renderInlineLocation = React30.useMemo(
62071
+ () => buildLocation(false),
62072
+ [buildLocation]
62073
+ );
62056
62074
  const renderImage = React30.useMemo(() => {
62057
62075
  if (imageSlot2) return imageSlot2;
62058
62076
  if (!image) return null;
@@ -62081,6 +62099,7 @@ function HeroEventRegistration({
62081
62099
  }, [imageSlot2, image, imageClassName, optixFlowConfig, renderLocation]);
62082
62100
  const desktopOrder = directionConfig.desktop === "mediaRight" ? "md:flex-row" : "md:flex-row-reverse";
62083
62101
  const mobileOrder = directionConfig.mobile === "mediaTop" ? "flex-col-reverse" : "flex-col";
62102
+ const locationInImage = !imageSlot2 && Boolean(image);
62084
62103
  return /* @__PURE__ */ jsxRuntime.jsx(
62085
62104
  Section,
62086
62105
  {
@@ -62129,6 +62148,7 @@ function HeroEventRegistration({
62129
62148
  }
62130
62149
  ) : heading),
62131
62150
  description && (typeof description === "string" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: cn("text-lg text-balance", descriptionClassName), children: description }) : description),
62151
+ !locationInImage && renderInlineLocation,
62132
62152
  /* @__PURE__ */ jsxRuntime.jsx(
62133
62153
  BlockActions,
62134
62154
  {
@@ -113896,6 +113916,7 @@ var NAVBAR_BLOCK_CONTRACTS = {
113896
113916
  };
113897
113917
  var TESTIMONIALS_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
113898
113918
  var TESTIMONIALS_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
113919
+ var TESTIMONIALS_FEED_NOTE = "Dynamic feeds (Phase 2): this block can carry a block-level dataSource of type 'testimonials_feed', hydrated at routing-build time into TestimonialItem[]. Bind target (dataSource.bindTo) defaults to 'testimonials'; use 'reviews' for testimonials-list-verified, testimonials-images-helpful, and testimonials-grid-add-review; single-item 'testimonial' (an object \u2014 the hydrator binds items[0]) for testimonials-company-logo, testimonials-large-quote, and testimonials-split-image. Wire mapping: content -> quote, reviewer_name -> author, rating -> rating (only when numeric; never fabricate), profile_url + platform -> linkConfig. Avatars are intentionally NOT mapped in Phase 2 because review avatar URLs are hotlinked and rot-prone. The reviews_or_testimonials capability is satisfied by live reviews OR authored static quotes, so octane does not hard-drop testimonials blocks on the capability alone.";
113899
113920
  var testimonialsCapabilities = (...capabilities) => capabilities;
113900
113921
  var img = TESTIMONIALS_EXAMPLE_IMAGE_URL;
113901
113922
  var TESTIMONIALS_BLOCK_CONTRACTS = {
@@ -113939,7 +113960,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
113939
113960
  background="white"
113940
113961
  />
113941
113962
  `.trim(),
113942
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. Each review needs a rating (1\u20135), a title, content, and an author. The verified flag should only be set when the review platform actually verifies purchases. avatarSrc must be an absolute image URL.",
113963
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. Each review needs a rating (1\u20135), a title, content, and an author. The verified flag should only be set when the review platform actually verifies purchases. avatarSrc must be an absolute image URL. The verified indicator renders ONLY when verifiedPurchaseLabel is supplied \u2014 there is NO fallback wording; choose a label that fits the client's industry (e.g. 'Verified' / 'Verified Diner' / 'Verified Guest') or omit the prop to hide the indicator entirely.",
113943
113964
  usageRequirements: {
113944
113965
  requiredProps: ["reviews"],
113945
113966
  propConstraints: {
@@ -114011,7 +114032,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
114011
114032
  exampleUsage: `
114012
114033
  <TestimonialsImagesHelpful
114013
114034
  heading="Customer Reviews"
114014
- verifiedPurchaseLabel="Verified Purchase"
114035
+ verifiedPurchaseLabel="Verified"
114015
114036
  reviews={[
114016
114037
  {
114017
114038
  rating: 5,
@@ -114037,7 +114058,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
114037
114058
  ]}
114038
114059
  />
114039
114060
  `.trim(),
114040
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. The images[] array holds supplementary review photos from the reviewer \u2014 these must be real product/experience photos, not stock imagery. helpful counts must reflect actual platform data.",
114061
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. The images[] array holds supplementary review photos from the reviewer \u2014 these must be real product/experience photos, not stock imagery. helpful counts must reflect actual platform data. The verified indicator renders ONLY when verifiedPurchaseLabel is supplied \u2014 there is NO fallback wording; choose a label that fits the client's industry (e.g. 'Verified' / 'Verified Diner' / 'Verified Guest') or omit the prop to hide the indicator entirely.",
114041
114062
  usageRequirements: {
114042
114063
  requiredProps: ["reviews"],
114043
114064
  propConstraints: {
@@ -114077,7 +114098,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
114077
114098
  },
114078
114099
  exampleProps: {
114079
114100
  heading: "Customer Reviews",
114080
- verifiedPurchaseLabel: "Verified Purchase",
114101
+ verifiedPurchaseLabel: "Verified",
114081
114102
  reviews: [
114082
114103
  {
114083
114104
  rating: 5,
@@ -115361,7 +115382,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
115361
115382
  spacing="xl"
115362
115383
  />
115363
115384
  `.trim(),
115364
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. backgroundIcon is a decorative Iconify icon ID (e.g., 'lucide/rocket') rendered as a large watermark behind the testimonial number \u2014 use icons that complement the testimonial theme. backgroundLabel is optional decorative text.",
115385
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. backgroundIcon is a decorative Iconify icon ID (e.g., 'lucide/rocket') rendered as a large watermark behind the testimonial number \u2014 use icons that complement the testimonial theme. backgroundLabel is optional decorative text. The vertical side label renders ONLY when verticalLabel is supplied \u2014 there is NO fallback wording; choose industry-appropriate text or omit the prop to hide it.",
115365
115386
  usageRequirements: {
115366
115387
  requiredProps: ["testimonials"],
115367
115388
  propConstraints: {
@@ -115503,6 +115524,9 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
115503
115524
  }
115504
115525
  }
115505
115526
  };
115527
+ for (const contract of Object.values(TESTIMONIALS_BLOCK_CONTRACTS)) {
115528
+ contract.importantUsageNotes = contract.importantUsageNotes ? `${contract.importantUsageNotes} ${TESTIMONIALS_FEED_NOTE}` : TESTIMONIALS_FEED_NOTE;
115529
+ }
115506
115530
  var FEATURES_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
115507
115531
  var FEATURES_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
115508
115532
  var featuresCapabilities = (...capabilities) => capabilities;
@@ -117507,6 +117531,7 @@ var CONTACT_BLOCK_CONTRACTS = {
117507
117531
  };
117508
117532
  var HERO_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
117509
117533
  var HERO_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
117534
+ var EVENTS_FEED_NOTE = "Dynamic feeds (Phase 4): this block can carry a block-level dataSource of type 'events_feed' (expands: true) \u2014 ONE symbolic block hydrates at routing-build time into N hero-event-registration instances, one per event occurrence (dataSource.limit default 6, hard cap 12). Wire mapping (\xA74.1d; never fabricate \u2014 omit any field with no real data): title -> heading, description -> description (omitted when blank), starts_at -> badgeText (short date badge, e.g. 'JUL 18'), starts_at in the event timezone -> locationLabel, location_name or custom_address -> locationSublabel (whichever is real; omitted when neither), image_url -> image ({src, alt: title}, only when an image exists), price_from -> stats[{value: '$X', label: 'From'}], recurring_summary -> stats[{value, label: 'Schedule'}], registration_url -> actions[{label: 'Register', href}]. stats, actions, and image are OMITTED entirely when no real data exists (the block null-guards every region; the propConstraints minItems/maxItems are AI-authoring constraints, not runtime requirements). The 'events' capability is SOFT: hero-event-registration is dual-use and remains legitimate for authored single-event / catering usage, so octane must NOT hard-drop the block on the capability alone \u2014 only feed-driven usage is gated.";
117510
117535
  var heroCapabilities = (...capabilities) => capabilities;
117511
117536
  var EXAMPLE_VIDEO_0 = {
117512
117537
  masterPlaylistUrl: "https://cdn.ing/assets/video/uploads/283393/hls/38865/master.m3u8",
@@ -121070,7 +121095,7 @@ var HERO_BLOCK_CONTRACTS = {
121070
121095
  background="dark"
121071
121096
  />
121072
121097
  `.trim(),
121073
- importantUsageNotes: `Only use if you have real event details. Only supply real stats and location data \u2014 do not fabricate. Requires a real 'image'. Do not exceed 50 characters for 'heading'. Do not exceed 130 characters for 'description'. ${HERO_MEDIA_NOTE}`,
121098
+ importantUsageNotes: `Only use if you have real event details. Only supply real stats and location data \u2014 do not fabricate. Image strongly preferred; omit when none exists \u2014 never use a stock/unrelated image. Do not exceed 50 characters for 'heading'. Do not exceed 130 characters for 'description'. ${HERO_MEDIA_NOTE} ${EVENTS_FEED_NOTE}`,
121074
121099
  usageRequirements: {
121075
121100
  requiredProps: ["heading"],
121076
121101
  propConstraints: {
@@ -135807,7 +135832,7 @@ function createBuilderContractBundle({
135807
135832
  symbolic: true,
135808
135833
  hydrationOwner: "dashtrack-ai",
135809
135834
  hydrationPhase: "routing-build",
135810
- canonicalPayloadExpectation: "Keep testimonials feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135835
+ canonicalPayloadExpectation: "Keep testimonials feed requests symbolic in canonical page JSON until routing-build hydration resolves them into TestimonialItem[] (or a single TestimonialItem for single-bind blocks). Wire mapping: content -> quote, reviewer_name -> author, rating -> rating (only when numeric; never fabricated), profile_url + platform -> linkConfig; avatars are intentionally left unmapped in Phase 2 because review avatar URLs are hotlinked and rot-prone.",
135811
135836
  requiredFields: ["type"],
135812
135837
  optionalFields: ["limit", "minRating", "platforms", "locationId", "bindTo"]
135813
135838
  },
@@ -135825,7 +135850,7 @@ function createBuilderContractBundle({
135825
135850
  symbolic: true,
135826
135851
  hydrationOwner: "dashtrack-ai",
135827
135852
  hydrationPhase: "routing-build",
135828
- canonicalPayloadExpectation: "Keep events feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135853
+ canonicalPayloadExpectation: "Keep events feed requests symbolic in canonical page JSON until routing-build hydration resolves them. events_feed is the first EXPANDING source (expands: true): ONE symbolic block hydrates into N hero-event-registration block instances, one per event occurrence (limit default 6, hard cap 12). Wire mapping (\xA74.1d; never fabricate \u2014 omit any field with no real data): title -> heading, description -> description (omitted when blank), starts_at -> badgeText (short date badge, e.g. 'JUL 18'), starts_at in the event timezone -> locationLabel, location_name or custom_address -> locationSublabel, image_url -> image ({src, alt: title}, only when present), price_from -> stats[{value: '$X', label: 'From'}], recurring_summary -> stats[{value, label: 'Schedule'}], registration_url -> actions[{label: 'Register', href}]. stats, actions, and image are omitted entirely when no real data exists. The 'events' capability is soft: hero-event-registration is dual-use (authored single-event/catering usage stays legitimate), so octane must not hard-drop on the capability alone.",
135829
135854
  requiredFields: ["type"],
135830
135855
  optionalFields: ["limit", "upcomingOnly", "locationIds", "bindTo"],
135831
135856
  expands: true
package/dist/registry.js CHANGED
@@ -44404,10 +44404,9 @@ function TestimonialsListVerified({
44404
44404
  ] }),
44405
44405
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-base flex-col items-start", children: [
44406
44406
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: review.author }) : review.author),
44407
- review.verified && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
44407
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
44408
44408
  /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
44409
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
44410
- !verifiedPurchaseLabel && /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Verified Purchase" })
44409
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
44411
44410
  ] })
44412
44411
  ] })
44413
44412
  ]
@@ -44592,10 +44591,9 @@ function TestimonialsImagesHelpful({
44592
44591
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0", children: [
44593
44592
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-col md:flex-row", children: [
44594
44593
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: review.author }) : review.author),
44595
- review.verified && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
44594
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
44596
44595
  /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
44597
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
44598
- !verifiedPurchaseLabel && /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Verified Purchase" })
44596
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
44599
44597
  ] })
44600
44598
  ] }),
44601
44599
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
@@ -45746,7 +45744,13 @@ function TestimonialsGridAddReview({
45746
45744
  const avatarSrc = getAvatarSrc(testimonial);
45747
45745
  return /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
45748
45746
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-4", children: [
45749
- /* @__PURE__ */ jsx(StarRating, { rating: 5, size: 20 }),
45747
+ /* @__PURE__ */ jsx(
45748
+ StarRating,
45749
+ {
45750
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
45751
+ size: 20
45752
+ }
45753
+ ),
45750
45754
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxs(
45751
45755
  "p",
45752
45756
  {
@@ -46708,7 +46712,13 @@ function TestimonialsStatsHeader({
46708
46712
  ),
46709
46713
  children: /* @__PURE__ */ jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
46710
46714
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-4", children: [
46711
- /* @__PURE__ */ jsx(StarRating, { rating: 5, size: 20 }),
46715
+ /* @__PURE__ */ jsx(
46716
+ StarRating,
46717
+ {
46718
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
46719
+ size: 20
46720
+ }
46721
+ ),
46712
46722
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxs(
46713
46723
  "p",
46714
46724
  {
@@ -48285,7 +48295,7 @@ function TestimonialsParallaxNumber({
48285
48295
  ),
48286
48296
  /* @__PURE__ */ jsxs("div", { className: "relative flex", children: [
48287
48297
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center border-r-0 md:border-r border-border/30 pr-4 md:pr-16", children: [
48288
- /* @__PURE__ */ jsxs(
48298
+ verticalLabel && /* @__PURE__ */ jsx(
48289
48299
  motion.span,
48290
48300
  {
48291
48301
  className: "text-sm tracking-widest uppercase",
@@ -48293,10 +48303,7 @@ function TestimonialsParallaxNumber({
48293
48303
  initial: { opacity: 0 },
48294
48304
  animate: { opacity: 1 },
48295
48305
  transition: { delay: 0.3 },
48296
- children: [
48297
- verticalLabel && (typeof verticalLabel === "string" ? verticalLabel : verticalLabel),
48298
- !verticalLabel && "Testimonials"
48299
- ]
48306
+ children: verticalLabel
48300
48307
  }
48301
48308
  ),
48302
48309
  /* @__PURE__ */ jsx("div", { className: "relative mt-8 h-32 w-4 bg-muted ring-2 ring-primary rounded", children: /* @__PURE__ */ jsx(
@@ -61917,6 +61924,7 @@ function HeroEventRegistration({
61917
61924
  }) {
61918
61925
  const renderBadge = useMemo(() => {
61919
61926
  if (badgeSlot) return badgeSlot;
61927
+ if (!badgeText && !badgeIcon) return null;
61920
61928
  return /* @__PURE__ */ jsxs(Badge, { className: "px-4 py-1 gap-2", children: [
61921
61929
  badgeIcon && /* @__PURE__ */ jsx(DynamicIcon, { name: badgeIcon, size: 18 }),
61922
61930
  badgeText
@@ -61963,56 +61971,66 @@ function HeroEventRegistration({
61963
61971
  }
61964
61972
  );
61965
61973
  }, [statsSlot, stats, statsClassName]);
61966
- const renderLocation = useMemo(() => {
61967
- if (locationSlot) return locationSlot;
61968
- if (!locationLabel && !locationSublabel) return null;
61969
- return /* @__PURE__ */ jsx(
61970
- "div",
61971
- {
61972
- className: cn(
61973
- "bg-card text-card-foreground",
61974
- "rounded-2xl p-2 md:p-4 shadow-lg",
61975
- "absolute bottom-0 translate-y-1/2",
61976
- "left-1/2 -translate-x-1/2 w-[90%]",
61977
- "md:-left-4 md:translate-x-0 md:w-auto",
61978
- "ring-4 ring-primary"
61979
- ),
61980
- children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
61981
- /* @__PURE__ */ jsx(
61982
- "div",
61983
- {
61984
- className: cn(
61985
- "flex items-center justify-center shrink-0",
61986
- "size-12 rounded-xl bg-primary text-primary-foreground"
61987
- ),
61988
- children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
61989
- }
61974
+ const buildLocation = React30.useCallback(
61975
+ (positioned) => {
61976
+ if (locationSlot) return locationSlot;
61977
+ if (!locationLabel && !locationSublabel) return null;
61978
+ return /* @__PURE__ */ jsx(
61979
+ "div",
61980
+ {
61981
+ className: cn(
61982
+ "bg-card text-card-foreground",
61983
+ "rounded-2xl p-2 md:p-4 shadow-lg",
61984
+ "ring-4 ring-primary",
61985
+ positioned ? cn(
61986
+ "absolute bottom-0 translate-y-1/2",
61987
+ "left-1/2 -translate-x-1/2 w-[90%]",
61988
+ "md:-left-4 md:translate-x-0 md:w-auto"
61989
+ ) : "w-full md:w-fit"
61990
61990
  ),
61991
- /* @__PURE__ */ jsxs(
61992
- "div",
61993
- {
61994
- className: cn(
61995
- "flex flex-col items-start justify-center",
61996
- "text-card-foreground gap-0 pr-0 md:pr-2"
61997
- ),
61998
- children: [
61999
- locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
62000
- locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsx(
62001
- "div",
62002
- {
62003
- className: cn(
62004
- "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
62005
- ),
62006
- children: locationSublabel
62007
- }
62008
- ) : locationSublabel)
62009
- ]
62010
- }
62011
- )
62012
- ] })
62013
- }
62014
- );
62015
- }, [locationSlot, locationLabel, locationSublabel]);
61991
+ children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
61992
+ /* @__PURE__ */ jsx(
61993
+ "div",
61994
+ {
61995
+ className: cn(
61996
+ "flex items-center justify-center shrink-0",
61997
+ "size-12 rounded-xl bg-primary text-primary-foreground"
61998
+ ),
61999
+ children: /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/map-pin", size: 24 })
62000
+ }
62001
+ ),
62002
+ /* @__PURE__ */ jsxs(
62003
+ "div",
62004
+ {
62005
+ className: cn(
62006
+ "flex flex-col items-start justify-center",
62007
+ "text-card-foreground gap-0 pr-0 md:pr-2"
62008
+ ),
62009
+ children: [
62010
+ locationLabel && (typeof locationLabel === "string" ? /* @__PURE__ */ jsx("div", { className: "text-sm md:text-base text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal font-semibold", children: locationLabel }) : locationLabel),
62011
+ locationSublabel && (typeof locationSublabel === "string" ? /* @__PURE__ */ jsx(
62012
+ "div",
62013
+ {
62014
+ className: cn(
62015
+ "text-xs md:text-sm text-balance leading-tight md:leading-normal tracking-tighter md:tracking-normal"
62016
+ ),
62017
+ children: locationSublabel
62018
+ }
62019
+ ) : locationSublabel)
62020
+ ]
62021
+ }
62022
+ )
62023
+ ] })
62024
+ }
62025
+ );
62026
+ },
62027
+ [locationSlot, locationLabel, locationSublabel]
62028
+ );
62029
+ const renderLocation = useMemo(() => buildLocation(true), [buildLocation]);
62030
+ const renderInlineLocation = useMemo(
62031
+ () => buildLocation(false),
62032
+ [buildLocation]
62033
+ );
62016
62034
  const renderImage = useMemo(() => {
62017
62035
  if (imageSlot2) return imageSlot2;
62018
62036
  if (!image) return null;
@@ -62041,6 +62059,7 @@ function HeroEventRegistration({
62041
62059
  }, [imageSlot2, image, imageClassName, optixFlowConfig, renderLocation]);
62042
62060
  const desktopOrder = directionConfig.desktop === "mediaRight" ? "md:flex-row" : "md:flex-row-reverse";
62043
62061
  const mobileOrder = directionConfig.mobile === "mediaTop" ? "flex-col-reverse" : "flex-col";
62062
+ const locationInImage = !imageSlot2 && Boolean(image);
62044
62063
  return /* @__PURE__ */ jsx(
62045
62064
  Section,
62046
62065
  {
@@ -62089,6 +62108,7 @@ function HeroEventRegistration({
62089
62108
  }
62090
62109
  ) : heading),
62091
62110
  description && (typeof description === "string" ? /* @__PURE__ */ jsx("p", { className: cn("text-lg text-balance", descriptionClassName), children: description }) : description),
62111
+ !locationInImage && renderInlineLocation,
62092
62112
  /* @__PURE__ */ jsx(
62093
62113
  BlockActions,
62094
62114
  {
@@ -113856,6 +113876,7 @@ var NAVBAR_BLOCK_CONTRACTS = {
113856
113876
  };
113857
113877
  var TESTIMONIALS_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
113858
113878
  var TESTIMONIALS_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
113879
+ var TESTIMONIALS_FEED_NOTE = "Dynamic feeds (Phase 2): this block can carry a block-level dataSource of type 'testimonials_feed', hydrated at routing-build time into TestimonialItem[]. Bind target (dataSource.bindTo) defaults to 'testimonials'; use 'reviews' for testimonials-list-verified, testimonials-images-helpful, and testimonials-grid-add-review; single-item 'testimonial' (an object \u2014 the hydrator binds items[0]) for testimonials-company-logo, testimonials-large-quote, and testimonials-split-image. Wire mapping: content -> quote, reviewer_name -> author, rating -> rating (only when numeric; never fabricate), profile_url + platform -> linkConfig. Avatars are intentionally NOT mapped in Phase 2 because review avatar URLs are hotlinked and rot-prone. The reviews_or_testimonials capability is satisfied by live reviews OR authored static quotes, so octane does not hard-drop testimonials blocks on the capability alone.";
113859
113880
  var testimonialsCapabilities = (...capabilities) => capabilities;
113860
113881
  var img = TESTIMONIALS_EXAMPLE_IMAGE_URL;
113861
113882
  var TESTIMONIALS_BLOCK_CONTRACTS = {
@@ -113899,7 +113920,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
113899
113920
  background="white"
113900
113921
  />
113901
113922
  `.trim(),
113902
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. Each review needs a rating (1\u20135), a title, content, and an author. The verified flag should only be set when the review platform actually verifies purchases. avatarSrc must be an absolute image URL.",
113923
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. Each review needs a rating (1\u20135), a title, content, and an author. The verified flag should only be set when the review platform actually verifies purchases. avatarSrc must be an absolute image URL. The verified indicator renders ONLY when verifiedPurchaseLabel is supplied \u2014 there is NO fallback wording; choose a label that fits the client's industry (e.g. 'Verified' / 'Verified Diner' / 'Verified Guest') or omit the prop to hide the indicator entirely.",
113903
113924
  usageRequirements: {
113904
113925
  requiredProps: ["reviews"],
113905
113926
  propConstraints: {
@@ -113971,7 +113992,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
113971
113992
  exampleUsage: `
113972
113993
  <TestimonialsImagesHelpful
113973
113994
  heading="Customer Reviews"
113974
- verifiedPurchaseLabel="Verified Purchase"
113995
+ verifiedPurchaseLabel="Verified"
113975
113996
  reviews={[
113976
113997
  {
113977
113998
  rating: 5,
@@ -113997,7 +114018,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
113997
114018
  ]}
113998
114019
  />
113999
114020
  `.trim(),
114000
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. The images[] array holds supplementary review photos from the reviewer \u2014 these must be real product/experience photos, not stock imagery. helpful counts must reflect actual platform data.",
114021
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. The images[] array holds supplementary review photos from the reviewer \u2014 these must be real product/experience photos, not stock imagery. helpful counts must reflect actual platform data. The verified indicator renders ONLY when verifiedPurchaseLabel is supplied \u2014 there is NO fallback wording; choose a label that fits the client's industry (e.g. 'Verified' / 'Verified Diner' / 'Verified Guest') or omit the prop to hide the indicator entirely.",
114001
114022
  usageRequirements: {
114002
114023
  requiredProps: ["reviews"],
114003
114024
  propConstraints: {
@@ -114037,7 +114058,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
114037
114058
  },
114038
114059
  exampleProps: {
114039
114060
  heading: "Customer Reviews",
114040
- verifiedPurchaseLabel: "Verified Purchase",
114061
+ verifiedPurchaseLabel: "Verified",
114041
114062
  reviews: [
114042
114063
  {
114043
114064
  rating: 5,
@@ -115321,7 +115342,7 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
115321
115342
  spacing="xl"
115322
115343
  />
115323
115344
  `.trim(),
115324
- importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. backgroundIcon is a decorative Iconify icon ID (e.g., 'lucide/rocket') rendered as a large watermark behind the testimonial number \u2014 use icons that complement the testimonial theme. backgroundLabel is optional decorative text.",
115345
+ importantUsageNotes: "Testimonials must come from real, sourced reviews or testimonials. Do not fabricate quotes, names, or company affiliations. backgroundIcon is a decorative Iconify icon ID (e.g., 'lucide/rocket') rendered as a large watermark behind the testimonial number \u2014 use icons that complement the testimonial theme. backgroundLabel is optional decorative text. The vertical side label renders ONLY when verticalLabel is supplied \u2014 there is NO fallback wording; choose industry-appropriate text or omit the prop to hide it.",
115325
115346
  usageRequirements: {
115326
115347
  requiredProps: ["testimonials"],
115327
115348
  propConstraints: {
@@ -115463,6 +115484,9 @@ var TESTIMONIALS_BLOCK_CONTRACTS = {
115463
115484
  }
115464
115485
  }
115465
115486
  };
115487
+ for (const contract of Object.values(TESTIMONIALS_BLOCK_CONTRACTS)) {
115488
+ contract.importantUsageNotes = contract.importantUsageNotes ? `${contract.importantUsageNotes} ${TESTIMONIALS_FEED_NOTE}` : TESTIMONIALS_FEED_NOTE;
115489
+ }
115466
115490
  var FEATURES_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
115467
115491
  var FEATURES_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
115468
115492
  var featuresCapabilities = (...capabilities) => capabilities;
@@ -117467,6 +117491,7 @@ var CONTACT_BLOCK_CONTRACTS = {
117467
117491
  };
117468
117492
  var HERO_EXAMPLE_IMAGE_URL = "https://cdn.ing/assets/i/r/308196/g6bbn73f7gxal82uu49m9prfd0u8/workplace-in-cafe.webp";
117469
117493
  var HERO_MEDIA_NOTE = "All media src values must be absolute URLs to real assets; relative paths and placeholder media variables are not allowed.";
117494
+ var EVENTS_FEED_NOTE = "Dynamic feeds (Phase 4): this block can carry a block-level dataSource of type 'events_feed' (expands: true) \u2014 ONE symbolic block hydrates at routing-build time into N hero-event-registration instances, one per event occurrence (dataSource.limit default 6, hard cap 12). Wire mapping (\xA74.1d; never fabricate \u2014 omit any field with no real data): title -> heading, description -> description (omitted when blank), starts_at -> badgeText (short date badge, e.g. 'JUL 18'), starts_at in the event timezone -> locationLabel, location_name or custom_address -> locationSublabel (whichever is real; omitted when neither), image_url -> image ({src, alt: title}, only when an image exists), price_from -> stats[{value: '$X', label: 'From'}], recurring_summary -> stats[{value, label: 'Schedule'}], registration_url -> actions[{label: 'Register', href}]. stats, actions, and image are OMITTED entirely when no real data exists (the block null-guards every region; the propConstraints minItems/maxItems are AI-authoring constraints, not runtime requirements). The 'events' capability is SOFT: hero-event-registration is dual-use and remains legitimate for authored single-event / catering usage, so octane must NOT hard-drop the block on the capability alone \u2014 only feed-driven usage is gated.";
117470
117495
  var heroCapabilities = (...capabilities) => capabilities;
117471
117496
  var EXAMPLE_VIDEO_0 = {
117472
117497
  masterPlaylistUrl: "https://cdn.ing/assets/video/uploads/283393/hls/38865/master.m3u8",
@@ -121030,7 +121055,7 @@ var HERO_BLOCK_CONTRACTS = {
121030
121055
  background="dark"
121031
121056
  />
121032
121057
  `.trim(),
121033
- importantUsageNotes: `Only use if you have real event details. Only supply real stats and location data \u2014 do not fabricate. Requires a real 'image'. Do not exceed 50 characters for 'heading'. Do not exceed 130 characters for 'description'. ${HERO_MEDIA_NOTE}`,
121058
+ importantUsageNotes: `Only use if you have real event details. Only supply real stats and location data \u2014 do not fabricate. Image strongly preferred; omit when none exists \u2014 never use a stock/unrelated image. Do not exceed 50 characters for 'heading'. Do not exceed 130 characters for 'description'. ${HERO_MEDIA_NOTE} ${EVENTS_FEED_NOTE}`,
121034
121059
  usageRequirements: {
121035
121060
  requiredProps: ["heading"],
121036
121061
  propConstraints: {
@@ -135767,7 +135792,7 @@ function createBuilderContractBundle({
135767
135792
  symbolic: true,
135768
135793
  hydrationOwner: "dashtrack-ai",
135769
135794
  hydrationPhase: "routing-build",
135770
- canonicalPayloadExpectation: "Keep testimonials feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135795
+ canonicalPayloadExpectation: "Keep testimonials feed requests symbolic in canonical page JSON until routing-build hydration resolves them into TestimonialItem[] (or a single TestimonialItem for single-bind blocks). Wire mapping: content -> quote, reviewer_name -> author, rating -> rating (only when numeric; never fabricated), profile_url + platform -> linkConfig; avatars are intentionally left unmapped in Phase 2 because review avatar URLs are hotlinked and rot-prone.",
135771
135796
  requiredFields: ["type"],
135772
135797
  optionalFields: ["limit", "minRating", "platforms", "locationId", "bindTo"]
135773
135798
  },
@@ -135785,7 +135810,7 @@ function createBuilderContractBundle({
135785
135810
  symbolic: true,
135786
135811
  hydrationOwner: "dashtrack-ai",
135787
135812
  hydrationPhase: "routing-build",
135788
- canonicalPayloadExpectation: "Keep events feed requests symbolic in canonical page JSON until routing-build hydration resolves them.",
135813
+ canonicalPayloadExpectation: "Keep events feed requests symbolic in canonical page JSON until routing-build hydration resolves them. events_feed is the first EXPANDING source (expands: true): ONE symbolic block hydrates into N hero-event-registration block instances, one per event occurrence (limit default 6, hard cap 12). Wire mapping (\xA74.1d; never fabricate \u2014 omit any field with no real data): title -> heading, description -> description (omitted when blank), starts_at -> badgeText (short date badge, e.g. 'JUL 18'), starts_at in the event timezone -> locationLabel, location_name or custom_address -> locationSublabel, image_url -> image ({src, alt: title}, only when present), price_from -> stats[{value: '$X', label: 'From'}], recurring_summary -> stats[{value, label: 'Schedule'}], registration_url -> actions[{label: 'Register', href}]. stats, actions, and image are omitted entirely when no real data exists. The 'events' capability is soft: hero-event-registration is dual-use (authored single-event/catering usage stays legitimate), so octane must not hard-drop on the capability alone.",
135789
135814
  requiredFields: ["type"],
135790
135815
  optionalFields: ["limit", "upcomingOnly", "locationIds", "bindTo"],
135791
135816
  expands: true
@@ -669,7 +669,13 @@ function TestimonialsGridAddReview({
669
669
  const avatarSrc = getAvatarSrc(testimonial);
670
670
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
671
671
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
672
- /* @__PURE__ */ jsxRuntime.jsx(StarRating, { rating: 5, size: 20 }),
672
+ /* @__PURE__ */ jsxRuntime.jsx(
673
+ StarRating,
674
+ {
675
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
676
+ size: 20
677
+ }
678
+ ),
673
679
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsxs(
674
680
  "p",
675
681
  {
@@ -647,7 +647,13 @@ function TestimonialsGridAddReview({
647
647
  const avatarSrc = getAvatarSrc(testimonial);
648
648
  return /* @__PURE__ */ jsx(Card, { className: cardClassName, children: /* @__PURE__ */ jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
649
649
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-4", children: [
650
- /* @__PURE__ */ jsx(StarRating, { rating: 5, size: 20 }),
650
+ /* @__PURE__ */ jsx(
651
+ StarRating,
652
+ {
653
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
654
+ size: 20
655
+ }
656
+ ),
651
657
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxs(
652
658
  "p",
653
659
  {
@@ -636,10 +636,9 @@ function TestimonialsImagesHelpful({
636
636
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-0", children: [
637
637
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-col md:flex-row", children: [
638
638
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: review.author }) : review.author),
639
- review.verified && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
639
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
640
640
  /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
641
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
642
- !verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: "Verified Purchase" })
641
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
643
642
  ] })
644
643
  ] }),
645
644
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
@@ -613,10 +613,9 @@ function TestimonialsImagesHelpful({
613
613
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-0", children: [
614
614
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-col md:flex-row", children: [
615
615
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: review.author }) : review.author),
616
- review.verified && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
616
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
617
617
  /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
618
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
619
- !verifiedPurchaseLabel && /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Verified Purchase" })
618
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
620
619
  ] })
621
620
  ] }),
622
621
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm", children: [
@@ -558,10 +558,9 @@ function TestimonialsListVerified({
558
558
  ] }),
559
559
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 text-base flex-col items-start", children: [
560
560
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: review.author }) : review.author),
561
- review.verified && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
561
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
562
562
  /* @__PURE__ */ jsxRuntime.jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
563
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
564
- !verifiedPurchaseLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: "Verified Purchase" })
563
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
565
564
  ] })
566
565
  ] })
567
566
  ]
@@ -535,10 +535,9 @@ function TestimonialsListVerified({
535
535
  ] }),
536
536
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 text-base flex-col items-start", children: [
537
537
  review.author && (typeof review.author === "string" ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: review.author }) : review.author),
538
- review.verified && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
538
+ review.verified && verifiedPurchaseLabel && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
539
539
  /* @__PURE__ */ jsx(DynamicIcon, { name: "lucide/badge-check", size: 18 }),
540
- verifiedPurchaseLabel && (typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel),
541
- !verifiedPurchaseLabel && /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Verified Purchase" })
540
+ typeof verifiedPurchaseLabel === "string" ? /* @__PURE__ */ jsx("span", { className: "text-sm", children: verifiedPurchaseLabel }) : verifiedPurchaseLabel
542
541
  ] })
543
542
  ] })
544
543
  ]
@@ -554,7 +554,7 @@ function TestimonialsParallaxNumber({
554
554
  ),
555
555
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex", children: [
556
556
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center border-r-0 md:border-r border-border/30 pr-4 md:pr-16", children: [
557
- /* @__PURE__ */ jsxRuntime.jsxs(
557
+ verticalLabel && /* @__PURE__ */ jsxRuntime.jsx(
558
558
  framerMotion.motion.span,
559
559
  {
560
560
  className: "text-sm tracking-widest uppercase",
@@ -562,10 +562,7 @@ function TestimonialsParallaxNumber({
562
562
  initial: { opacity: 0 },
563
563
  animate: { opacity: 1 },
564
564
  transition: { delay: 0.3 },
565
- children: [
566
- verticalLabel && (typeof verticalLabel === "string" ? verticalLabel : verticalLabel),
567
- !verticalLabel && "Testimonials"
568
- ]
565
+ children: verticalLabel
569
566
  }
570
567
  ),
571
568
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mt-8 h-32 w-4 bg-muted ring-2 ring-primary rounded", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -533,7 +533,7 @@ function TestimonialsParallaxNumber({
533
533
  ),
534
534
  /* @__PURE__ */ jsxs("div", { className: "relative flex", children: [
535
535
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center border-r-0 md:border-r border-border/30 pr-4 md:pr-16", children: [
536
- /* @__PURE__ */ jsxs(
536
+ verticalLabel && /* @__PURE__ */ jsx(
537
537
  motion.span,
538
538
  {
539
539
  className: "text-sm tracking-widest uppercase",
@@ -541,10 +541,7 @@ function TestimonialsParallaxNumber({
541
541
  initial: { opacity: 0 },
542
542
  animate: { opacity: 1 },
543
543
  transition: { delay: 0.3 },
544
- children: [
545
- verticalLabel && (typeof verticalLabel === "string" ? verticalLabel : verticalLabel),
546
- !verticalLabel && "Testimonials"
547
- ]
544
+ children: verticalLabel
548
545
  }
549
546
  ),
550
547
  /* @__PURE__ */ jsx("div", { className: "relative mt-8 h-32 w-4 bg-muted ring-2 ring-primary rounded", children: /* @__PURE__ */ jsx(
@@ -689,7 +689,13 @@ function TestimonialsStatsHeader({
689
689
  ),
690
690
  children: /* @__PURE__ */ jsxRuntime.jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
691
691
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-start gap-4", children: [
692
- /* @__PURE__ */ jsxRuntime.jsx(StarRating, { rating: 5, size: 20 }),
692
+ /* @__PURE__ */ jsxRuntime.jsx(
693
+ StarRating,
694
+ {
695
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
696
+ size: 20
697
+ }
698
+ ),
693
699
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxRuntime.jsxs(
694
700
  "p",
695
701
  {
@@ -667,7 +667,13 @@ function TestimonialsStatsHeader({
667
667
  ),
668
668
  children: /* @__PURE__ */ jsx(CardContent, { className: "p-6", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-12 justify-between", children: [
669
669
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start gap-4", children: [
670
- /* @__PURE__ */ jsx(StarRating, { rating: 5, size: 20 }),
670
+ /* @__PURE__ */ jsx(
671
+ StarRating,
672
+ {
673
+ rating: typeof testimonial.rating === "number" ? testimonial.rating : 5,
674
+ size: 20
675
+ }
676
+ ),
671
677
  testimonial.quote && (typeof testimonial.quote === "string" ? /* @__PURE__ */ jsxs(
672
678
  "p",
673
679
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensite/ui",
3
- "version": "3.12.2",
3
+ "version": "3.14.0",
4
4
  "description": "Foundational UI component library for OpenSite Semantic Site Builder with tree-shakable exports and abstract styling",
5
5
  "keywords": [
6
6
  "react",