diff --git a/modules/blox/blox/cta-card/component.jsx b/modules/blox/blox/cta-card/component.jsx index 58c927c2..c93433aa 100644 --- a/modules/blox/blox/cta-card/component.jsx +++ b/modules/blox/blox/cta-card/component.jsx @@ -47,25 +47,22 @@ export const CtaCardBlock = ({content, design, _id, button_icon_svg}) => { // Legacy detection: design.background.gradient.start using saturated Tailwind tokens const bg = design?.background || {}; const gradientStart = bg.gradient?.start || ""; - const legacyDarkBg = ["primary-7", "primary-8", "primary-9", "secondary-7", "secondary-8", "secondary-9"].some((p) => gradientStart.startsWith(p)); - textColorMode = (looksColored || legacyDarkBg) ? "light" : "dark"; + const legacyDarkBg = ["primary-7", "primary-8", "primary-9", "secondary-7", "secondary-8", "secondary-9"].some((p) => + gradientStart.startsWith(p), + ); + textColorMode = looksColored || legacyDarkBg ? "light" : "dark"; } // Title, body, and button classes — full literals only - const titleClasses = textColorMode === "light" - ? "text-white" - : "text-gray-900 dark:text-white"; - const bodyClasses = textColorMode === "light" - ? "text-white/80" - : "text-gray-700 dark:text-gray-300"; + const titleClasses = textColorMode === "light" ? "text-white" : "text-gray-900 dark:text-white"; + const bodyClasses = textColorMode === "light" ? "text-white/80" : "text-gray-700 dark:text-gray-300"; // Button: in "light" mode (coloured card), keep a white pill across both themes for consistency. // In "dark" mode (light card), use a dark pill. - const buttonBgClasses = textColorMode === "light" - ? "bg-white ring-1 ring-white/40 hover:bg-white/95 hover:ring-white/60 shadow-lg" - : "bg-gray-900 dark:bg-white ring-1 ring-gray-900/10 dark:ring-white/10 hover:bg-gray-800 dark:hover:bg-gray-100 shadow-lg"; - const buttonTextClasses = textColorMode === "light" - ? "text-gray-900" - : "text-white dark:text-gray-900"; + const buttonBgClasses = + textColorMode === "light" + ? "bg-white ring-1 ring-white/40 hover:bg-white/95 hover:ring-white/60 shadow-lg" + : "bg-gray-900 dark:bg-white ring-1 ring-gray-900/10 dark:ring-white/10 hover:bg-gray-800 dark:hover:bg-gray-100 shadow-lg"; + const buttonTextClasses = textColorMode === "light" ? "text-gray-900" : "text-white dark:text-gray-900"; // Button Logic const button = content?.button || {}; diff --git a/modules/blox/blox/cta-image-paragraph/component.jsx b/modules/blox/blox/cta-image-paragraph/component.jsx index 54f0114f..1700d32e 100644 --- a/modules/blox/blox/cta-image-paragraph/component.jsx +++ b/modules/blox/blox/cta-image-paragraph/component.jsx @@ -6,7 +6,6 @@ import {Icon} from "../../shared/components/Icon.jsx"; -// Simple markdown renderer function renderText(text) { if (!text) return ""; return String(text) @@ -16,23 +15,11 @@ function renderText(text) { .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); } -// Arrow icon for CTA buttons -const ArrowIcon = () => ( - -); +const ARROW_SVG = ``; function CtaItem({item, idx, imageData, featureIconSvg}) { const isReversed = idx % 2 === 1; - const hasImage = !!imageData; - const _featureIcon = item.feature_icon || "check"; - // Resolve button link const buttonUrl = item.button?.url || ""; let isExternal = false; if (buttonUrl) { @@ -47,65 +34,78 @@ function CtaItem({item, idx, imageData, featureIconSvg}) { const features = Array.isArray(item.features) ? item.features : typeof item.features === "string" ? [item.features] : []; return ( -
+
{/* Image */} - {hasImage && - (imageData.srcset ? ( - {item.title - ) : ( - {item.title - ))} + {imageData && ( +
+
+ {imageData.srcset ? ( + {item.title + ) : ( + {item.title + )} +
+
+ )} {/* Text Content */} -
+
{item.title && (

)} {item.text && ( -

+

)} - {/* Feature List */} {features.length > 0 && ( -

    +
      {features.map((feature, fIdx) => ( -
    • - {featureIconSvg ? : null} - +
    • + + {featureIconSvg ? ( + + ) : ( + + )} + +
    • ))}
    )} - {/* CTA Button */} {item.button?.text && item.button?.url && ( {item.button.text} - + )}

diff --git a/modules/blox/blox/features/component.jsx b/modules/blox/blox/features/component.jsx index 20b618e2..3fdecabb 100644 --- a/modules/blox/blox/features/component.jsx +++ b/modules/blox/blox/features/component.jsx @@ -5,7 +5,10 @@ function renderText(text) { return String(text) .replace(/\*\*(.*?)\*\*/g, "$1") .replace(/\*(.*?)\*/g, "$1") - .replace(/`(.*?)`/g, '$1'); + .replace( + /`(.*?)`/g, + '$1', + ); } function FeatureCard({item, iconSvg, imgData, variant = "grid", large = false}) { @@ -14,22 +17,18 @@ function FeatureCard({item, iconSvg, imgData, variant = "grid", large = false}) ? `relative h-full rounded-2xl ring-1 ring-gray-200 dark:ring-gray-700 bg-white dark:bg-gray-800/50 ${large ? "p-8 lg:p-10" : "p-6"} hover:ring-primary-300 dark:hover:ring-primary-600 hover:shadow-lg transition-all duration-300 overflow-hidden` : ""; - const iconWrapper = large - ? "w-14 h-14 lg:w-16 lg:h-16" - : "w-11 h-11 lg:w-12 lg:h-12"; - const iconSize = large - ? "height:1.75rem;width:auto" - : "height:1.4rem;width:auto"; + const iconWrapper = large ? "w-14 h-14 lg:w-16 lg:h-16" : "w-11 h-11 lg:w-12 lg:h-12"; + const iconSize = large ? "height:1.75rem;width:auto" : "height:1.4rem;width:auto"; - const titleSize = large - ? "text-2xl lg:text-3xl" - : isCard ? "text-lg lg:text-xl" : "text-xl"; + const titleSize = large ? "text-2xl lg:text-3xl" : isCard ? "text-lg lg:text-xl" : "text-xl"; const descSize = large ? "text-base lg:text-lg" : "text-sm lg:text-base"; return (
{iconSvg && ( -
+
)} @@ -40,19 +39,11 @@ function FeatureCard({item, iconSvg, imgData, variant = "grid", large = false}) /> )} {item.description && ( -

+

)} {large && imgData?.src && (

- {item.name + {item.name
)}
@@ -63,13 +54,7 @@ function GridLayout({items, iconMap, item_images}) { return (
{items.map((item, idx) => ( - + ))}
); @@ -109,31 +94,23 @@ export const FeaturesBlock = ({content = {}, design = {}, icon_svgs = {}, item_i
{(title || text || subtitle) && (
- {subtitle && ( -

- {subtitle} -

- )} + {subtitle &&

{subtitle}

} {title && (

)} - {text && ( -

- )} + {text &&

}

)} - {items.length > 0 && ( - layout === "bento" - ? - : - )} + {items.length > 0 && + (layout === "bento" ? ( + + ) : ( + + ))}
); diff --git a/modules/blox/blox/logos/component.jsx b/modules/blox/blox/logos/component.jsx index e544de28..c2b8d0e4 100644 --- a/modules/blox/blox/logos/component.jsx +++ b/modules/blox/blox/logos/component.jsx @@ -13,17 +13,17 @@ const MARQUEE_CSS = ` `; // Slot widths keep every logo at a consistent visual footprint regardless of SVG aspect ratio -const SLOT_W = {sm: "w-24", md: "w-32", lg: "w-40"}; +const SLOT_W = {sm: "w-24", md: "w-32", lg: "w-40"}; // Max-width caps very wide SVGs inside their slot (height × ~2.5) -const MAX_W = {sm: "3.5rem", md: "5rem", lg: "7rem"}; +const MAX_W = {sm: "3.5rem", md: "5rem", lg: "7rem"}; // Heights passed as inline style so Icon.jsx's auto-sizing fires correctly -const HEIGHT = {sm: "1.75rem", md: "2.5rem", lg: "3.5rem"}; +const HEIGHT = {sm: "1.75rem", md: "2.5rem", lg: "3.5rem"}; // Full-string Tailwind filter classes — no dynamic concatenation const FILTER = { grayscale: "grayscale opacity-60 hover:grayscale-0 hover:opacity-100 transition-all duration-300", - white: "brightness-0 invert transition-all duration-300", - color: "transition-all duration-300", + white: "brightness-0 invert transition-all duration-300", + color: "transition-all duration-300", }; function renderText(text) { @@ -34,18 +34,18 @@ function renderText(text) { } function LogoItem({item, idx, logoStyle, logoSize, icon_svgs, item_images, padY = "py-3"}) { - const iconSvg = item.icon ? (icon_svgs?.[item.icon] ?? null) : null; + const iconSvg = item.icon ? (icon_svgs?.[item.icon] ?? null) : null; const imgData = item_images?.[String(idx)] ?? null; - const height = HEIGHT[logoSize] ?? HEIGHT.md; - const maxW = MAX_W[logoSize] ?? MAX_W.md; - const slotW = SLOT_W[logoSize] ?? SLOT_W.md; + const height = HEIGHT[logoSize] ?? HEIGHT.md; + const maxW = MAX_W[logoSize] ?? MAX_W.md; + const slotW = SLOT_W[logoSize] ?? SLOT_W.md; // Per-item overrides: style and scale const itemStyle = item.style ?? logoStyle; - const filter = FILTER[itemStyle] ?? FILTER.grayscale; + const filter = FILTER[itemStyle] ?? FILTER.grayscale; // scale: compensates for SVG intrinsic whitespace or atypical artwork areas. // e.g. scale: 1.3 enlarges a logo whose SVG has excessive padding baked into its viewBox. - const scale = typeof item.scale === "number" ? item.scale : 1; + const scale = typeof item.scale === "number" ? item.scale : 1; const scaleStyle = scale !== 1 ? `;transform:scale(${scale})` : ""; let visual; @@ -80,12 +80,13 @@ function LogoItem({item, idx, logoStyle, logoSize, icon_svgs, item_images, padY const cls = `flex items-center justify-center flex-shrink-0 ${slotW} ${padY}`; return item.url ? ( - + {visual} ) : ( - + ); } @@ -93,8 +94,7 @@ function LogoRow({items, logoStyle, logoSize, icon_svgs, item_images}) { return (
{items.map((item, i) => ( - + ))}
); @@ -102,15 +102,17 @@ function LogoRow({items, logoStyle, logoSize, icon_svgs, item_images}) { function LogoGrid({items, logoStyle, logoSize, icon_svgs, item_images}) { const cols = - items.length <= 3 ? "grid-cols-3" : - items.length <= 4 ? "grid-cols-2 sm:grid-cols-4" : - items.length <= 6 ? "grid-cols-3 sm:grid-cols-6" : - "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"; + items.length <= 3 + ? "grid-cols-3" + : items.length <= 4 + ? "grid-cols-2 sm:grid-cols-4" + : items.length <= 6 + ? "grid-cols-3 sm:grid-cols-6" + : "grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"; return (
{items.map((item, i) => ( - + ))}
); @@ -125,17 +127,18 @@ function LogoMarquee({items, logoStyle, logoSize, icon_svgs, item_images, speed} mask-image fades edges without depending on the section background colour — works on any bg (white, gray-50, dark, gradient, etc.) */} -
-
+
+
{doubled.map((item, i) => ( - + ))}
@@ -144,15 +147,13 @@ function LogoMarquee({items, logoStyle, logoSize, icon_svgs, item_images, speed} } export const LogosBlock = ({content = {}, design = {}, icon_svgs = {}, item_images = {}}) => { - const rawItems = Array.isArray(content.items) ? content.items - : Array.isArray(content.logos) ? content.logos - : []; + const rawItems = Array.isArray(content.items) ? content.items : Array.isArray(content.logos) ? content.logos : []; const {title, subtitle, cta} = content; - const layout = design.layout || design.display_mode || "row"; - const logoStyle = design.logo_style || "grayscale"; - const logoSize = design.logo_size || "md"; - const speed = design.marquee_speed ?? 30; + const layout = design.layout || design.display_mode || "row"; + const logoStyle = design.logo_style || "grayscale"; + const logoSize = design.logo_size || "md"; + const speed = design.marquee_speed ?? 30; const isExtCta = cta?.url && (cta.url.startsWith("http://") || cta.url.startsWith("https://")); @@ -167,26 +168,16 @@ export const LogosBlock = ({content = {}, design = {}, icon_svgs = {}, item_imag dangerouslySetInnerHTML={{__html: renderText(title)}} /> )} - {subtitle && ( -

- )} + {subtitle &&

}

)} {layout === "marquee" && ( - - )} - {layout === "grid" && ( - + )} + {layout === "grid" && } {layout !== "marquee" && layout !== "grid" && ( - + )} {cta?.text && cta?.url && ( diff --git a/modules/blox/blox/pricing/component.jsx b/modules/blox/blox/pricing/component.jsx index c2e8ae92..c5807f87 100644 --- a/modules/blox/blox/pricing/component.jsx +++ b/modules/blox/blox/pricing/component.jsx @@ -16,7 +16,9 @@ function BillingToggle({billing, setBilling, toggle}) { const isYearly = billing === "yearly"; return (
- + {toggle.monthly_label || "Monthly"} - + {toggle.yearly_label || "Yearly"} {toggle.yearly_discount && ( @@ -47,17 +53,12 @@ function PriceDisplay({price = {}, price_suffix, price_note, price_note_monthly, const isContact = raw === "" || raw == null; const isFree = String(raw) === "0"; - // billing-aware note: per-billing field → static fallback - const note = billing === "yearly" - ? (price_note_yearly ?? price_note) - : (price_note_monthly ?? price_note); + const note = billing === "yearly" ? (price_note_yearly ?? price_note) : (price_note_monthly ?? price_note); if (isContact) { return (
-

- {price_note || "Contact us"} -

+

{price_note || "Contact us"}

); } @@ -85,7 +86,7 @@ function PriceDisplay({price = {}, price_suffix, price_note, price_note_monthly, function FeatureRow({feature}) { const text = typeof feature === "string" ? feature : (feature.text ?? ""); - const included = typeof feature === "string" ? true : (feature.included !== false); + const included = typeof feature === "string" ? true : feature.included !== false; const note = typeof feature === "object" ? feature.note : null; return ( @@ -102,6 +103,66 @@ function FeatureRow({feature}) { ); } +function TierCardContent({ + name, + description, + price, + price_suffix, + price_note, + price_note_monthly, + price_note_yearly, + cta, + features, + billing, + icon_svgs, + highlight, +}) { + const ctaStyle = cta.style || (highlight ? "primary" : "outline"); + const ctaIconSvg = cta.icon ? (icon_svgs?.[cta.icon] ?? null) : null; + const isExternalCta = cta.url && (cta.url.startsWith("http://") || cta.url.startsWith("https://")); + + return ( + <> +
+

{name}

+ {description &&

{description}

} +
+ + + + {cta.text && cta.url && ( + + {cta.text} + {ctaIconSvg && } + + )} + + {features.length > 0 && ( +
    + {features.map((f, i) => ( + + ))} +
+ )} + + ); +} + function PricingTier({tier, billing, icon_svgs}) { const { name = "", @@ -117,62 +178,49 @@ function PricingTier({tier, billing, icon_svgs}) { features = [], } = tier; - const ctaStyle = cta.style || (highlight ? "primary" : "outline"); - const ctaIconSvg = cta.icon ? (icon_svgs?.[cta.icon] ?? null) : null; - const isExternalCta = cta.url && (cta.url.startsWith("http://") || cta.url.startsWith("https://")); + const cardProps = { + name, + description, + price, + price_suffix, + price_note, + price_note_monthly, + price_note_yearly, + cta, + features, + billing, + icon_svgs, + highlight, + }; + + if (highlight) { + return ( +
+ {badge && ( +
+ + {badge} + +
+ )} + {/* Gradient border: 1px gradient ring via padding + colored wrapper */} +
+
+ +
+
+
+ ); + } return ( -
+
{badge && (
- - {badge} - + {badge}
)} - -
-

{name}

- {description && ( -

{description}

- )} -
- - - - {cta.text && cta.url && ( - - {cta.text} - {ctaIconSvg && } - - )} - - {features.length > 0 && ( -
    - {features.map((f, i) => )} -
- )} +
); } @@ -180,19 +228,13 @@ function PricingTier({tier, billing, icon_svgs}) { export const PricingBlock = ({content = {}, icon_svgs = {}}) => { const {title, subtitle, billing_toggle = {}, tiers = []} = content; - const hasYearlyPrices = tiers.some( - (t) => t.price?.yearly != null && t.price.yearly !== t.price.monthly - ); + const hasYearlyPrices = tiers.some((t) => t.price?.yearly != null && t.price.yearly !== t.price.monthly); const showToggle = billing_toggle.enabled && hasYearlyPrices; const [billing, setBilling] = useState("monthly"); const colsClass = - tiers.length === 1 - ? "max-w-sm mx-auto" - : tiers.length === 2 - ? "max-w-4xl mx-auto grid-cols-1 sm:grid-cols-2" - : "grid-cols-1 md:grid-cols-3"; + tiers.length === 1 ? "max-w-sm mx-auto" : tiers.length === 2 ? "max-w-4xl mx-auto grid-cols-1 sm:grid-cols-2" : "grid-cols-1 md:grid-cols-3"; return (
@@ -205,20 +247,13 @@ export const PricingBlock = ({content = {}, icon_svgs = {}}) => { dangerouslySetInnerHTML={{__html: renderText(title)}} /> )} - {subtitle && ( -

- )} + {subtitle &&

}

)} - {showToggle && ( - - )} + {showToggle && } - {/* pt-6 clears space for the absolutely-positioned badge on the highlighted tier */} + {/* pt-6 gives clearance for absolutely-positioned badges */}
{tiers.map((tier, i) => ( diff --git a/modules/blox/blox/stats/component.jsx b/modules/blox/blox/stats/component.jsx index 93936a40..200c8e2f 100644 --- a/modules/blox/blox/stats/component.jsx +++ b/modules/blox/blox/stats/component.jsx @@ -119,8 +119,9 @@ function gridColsClass(count) { // Gradient text uses bg-clip-text so colors come from the gradient utilities // (full classes for Tailwind's source scanner to pick up at build time) -const GRADIENT_NUMBER_CLS = "bg-gradient-to-br from-primary-600 via-primary-500 to-secondary-500 dark:from-primary-400 dark:via-primary-300 dark:to-secondary-400 bg-clip-text text-transparent"; -const SOLID_NUMBER_CLS = "text-gray-900 dark:text-white"; +const GRADIENT_NUMBER_CLS = + "bg-gradient-to-br from-primary-600 via-primary-500 to-secondary-500 dark:from-primary-400 dark:via-primary-300 dark:to-secondary-400 bg-clip-text text-transparent"; +const SOLID_NUMBER_CLS = "text-gray-900 dark:text-white"; // ─── Stat Item (Cards) ─────────────────────────────────────────────── function StatCard({item, iconSvg, numbersGradient}) { diff --git a/modules/blox/blox/steps/component.jsx b/modules/blox/blox/steps/component.jsx index 1e0d2e79..f81f9260 100644 --- a/modules/blox/blox/steps/component.jsx +++ b/modules/blox/blox/steps/component.jsx @@ -6,7 +6,11 @@ function renderText(text) { return String(text) .replace(/\*\*(.*?)\*\*/g, "$1") .replace(/\*(.*?)\*/g, "$1") - .replace(/`(.*?)`/g, (_, code) => `${code}`); + .replace( + /`(.*?)`/g, + (_, code) => + `${code}`, + ); } function toRoman(n) { @@ -47,9 +51,9 @@ function StepMarker({idx, iconSvg, markerStyle, numbering, size = "sm"}) { ); } - const dim = size === "lg" ? "w-14 h-14 text-base" : "w-10 h-10 text-sm"; + const dim = size === "lg" ? "w-14 h-14 text-base" : "w-10 h-10 text-sm"; const iconH = size === "lg" ? "1.75rem" : "1.25rem"; - const base = `${dim} flex items-center justify-center rounded-full flex-shrink-0 font-bold`; + const base = `${dim} flex items-center justify-center rounded-full flex-shrink-0 font-bold`; // Vertical/timeline icon — keep circle for visual anchoring against the connector line if (markerStyle === "icon" && iconSvg) { @@ -60,27 +64,17 @@ function StepMarker({idx, iconSvg, markerStyle, numbering, size = "sm"}) { ); } - return ( -
- {markerLabel(idx, numbering)} -
- ); + return
{markerLabel(idx, numbering)}
; } function StepBody({step, imgData}) { - const isExtCta = - step.cta?.url && - (step.cta.url.startsWith("http://") || step.cta.url.startsWith("https://")); + const isExtCta = step.cta?.url && (step.cta.url.startsWith("http://") || step.cta.url.startsWith("https://")); return ( <> {(step.badge || step.date) && (
- {step.date && ( - - {step.date} - - )} + {step.date && {step.date}} {step.badge && ( {step.badge} @@ -89,24 +83,10 @@ function StepBody({step, imgData}) {
)} {step.title && ( -

- )} - {step.text && ( -

- )} - {imgData && ( - {step.title +

)} + {step.text &&

} + {imgData && {step.title} {step.cta?.text && step.cta?.url && (

- + {!isLast && connector !== "none" && (
@@ -173,25 +146,12 @@ function HorizontalSteps({steps, markerStyle, numbering, connector, icon_svgs}) const iconSvg = step.icon ? (icon_svgs?.[step.icon] ?? null) : null; return (
  • - +
    {step.title && ( -

    - )} - {step.text && ( -

    +

    )} + {step.text &&

    } {step.cta?.text && step.cta?.url && ( )} @@ -224,13 +182,7 @@ function HorizontalSteps({steps, markerStyle, numbering, connector, icon_svgs}) const iconSvg = step.icon ? (icon_svgs?.[step.icon] ?? null) : null; return (

    ); })} @@ -246,16 +198,10 @@ function HorizontalSteps({steps, markerStyle, numbering, connector, icon_svgs}) )} {step.title && ( -

    +

    )} {step.text && ( -

    +

    )} {step.cta?.text && step.cta?.url && ( +

  • {/* Content card */} -
    +
    {(step.badge || step.date) && ( {/* Spacer on opposite side (desktop) */} @@ -436,11 +354,7 @@ export const StepsBlock = ({content = {}, design = {}, icon_svgs = {}, item_imag const containerRef = useRef(null); useStepAnimation(containerRef); - const steps = Array.isArray(content.items) - ? content.items - : Array.isArray(content.steps) - ? content.steps - : []; + const steps = Array.isArray(content.items) ? content.items : Array.isArray(content.steps) ? content.steps : []; const {title, subtitle, text} = content; const layout = design.layout || "vertical"; @@ -449,8 +363,7 @@ export const StepsBlock = ({content = {}, design = {}, icon_svgs = {}, item_imag const connector = design.connector || "line"; const isCentered = layout === "horizontal" || layout === "timeline"; - const maxW = - layout === "horizontal" ? "max-w-5xl" : layout === "timeline" ? "max-w-4xl" : "max-w-2xl"; + const maxW = layout === "horizontal" ? "max-w-5xl" : layout === "timeline" ? "max-w-4xl" : "max-w-2xl"; return (
    @@ -459,11 +372,7 @@ export const StepsBlock = ({content = {}, design = {}, icon_svgs = {}, item_imag
    {(title || subtitle || text) && (
    - {subtitle && ( -

    - {subtitle} -

    - )} + {subtitle &&

    {subtitle}

    } {title && (

    + )} {layout === "timeline" && ( - + )} {layout !== "horizontal" && layout !== "timeline" && ( ( - -); - -// Simple markdown renderer function renderText(text) { if (!text) return ""; return String(text) @@ -26,23 +13,74 @@ function renderText(text) { .replace(/`(.*?)`/g, "$1"); } -function TestimonialItem({item, avatarUrl, isLast}) { +function Avatar({item, avatarUrl, size = "lg"}) { + const sizeClass = size === "lg" ? "w-14 h-14" : "w-10 h-10"; + const textSize = size === "lg" ? "text-xl" : "text-base"; + const initial = item.name ? item.name.trim()[0].toUpperCase() : "?"; + + if (avatarUrl) { + return ( + {item.name + ); + } + return ( -
    - -
    -

    +

    + {initial} +
    + ); +} + +function TestimonialFeatured({item, avatarUrl}) { + return ( +
    + {/* Decorative large quote mark */} + + +
    +

    -
    - {/* Avatar */} - {avatarUrl ? ( - {item.name - ) : ( -
    - )} -
    - {item.name &&
    {item.name}
    } - {item.role &&
    {item.role}
    } + +
    + +
    + {item.name &&
    {item.name}
    } + {item.role &&
    {item.role}
    } +
    +
    +
    + ); +} + +function TestimonialCard({item, avatarUrl}) { + return ( +
    +
    +

    +

    + +
    + +
    + {item.name &&
    {item.name}
    } + {item.role &&
    {item.role}
    }
    @@ -55,27 +93,36 @@ export const TestimonialsBlock = ({content, _design, _id, item_images}) => { const items = Array.isArray(content?.items) ? content.items : []; const imageMap = item_images || {}; - return ( -
    -
    - {/* Section Header */} - {title && ( -
    -
    -

    - {text &&

    } -

    -
    - )} + const gridCols = + items.length === 1 + ? "" + : items.length === 2 + ? "grid grid-cols-1 sm:grid-cols-2 gap-6 max-w-4xl mx-auto" + : "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6"; - {/* Testimonial Items */} - {items.map((item, idx) => ( - - ))} -
    -
    + return ( +
    + {(title || text) && ( +
    + {title && ( +

    + )} + {text &&

    } +

    + )} + + {items.length === 1 ? ( + + ) : ( +
    + {items.map((item, idx) => ( + + ))} +
    + )} +
    ); }; diff --git a/modules/blox/layouts/_partials/functions/parse_block_v3.html b/modules/blox/layouts/_partials/functions/parse_block_v3.html index e91a7f56..f784c646 100644 --- a/modules/blox/layouts/_partials/functions/parse_block_v3.html +++ b/modules/blox/layouts/_partials/functions/parse_block_v3.html @@ -55,6 +55,16 @@ {{ $gradient_mesh := $bg.gradient_mesh | default dict }} {{ if $gradient_mesh.enable }} {{ $has_gradient_mesh = true }} + + {{/* Stacking-fix: the mesh wrapper sits at z-index:-10, while .home-section-bg sits at z-index:-1. + If we leave bg-color on home-section-bg, the colour paints OVER the mesh and the orbs are hidden. + Migrate the bg-color off home-section-bg and onto the section element itself, so the section's + own background paints BEHIND the mesh layer (orbs visible) and the radial gradient + image layers + on home-section-bg paint ABOVE the mesh (still visible). */}} + {{ if $style_bg }} + {{ $style = print $style $style_bg }} + {{ $style_bg = "" }} + {{ end }} {{ $mesh_style := $gradient_mesh.style | default "orbs" }} {{ $mesh_animation_raw := $gradient_mesh.animation | default "pulse" }} {{ $mesh_intensity := $gradient_mesh.intensity | default "subtle" }}