@opensite/ui 3.5.0 → 3.5.3

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,146 +1,17 @@
1
1
  "use client";
2
- import * as React from 'react';
3
- import React__default, { useRef, useState, useEffect, useMemo } from 'react';
4
- import Autoplay from 'embla-carousel-autoplay';
2
+ import * as React3 from 'react';
3
+ import React3__default, { useMemo } from 'react';
5
4
  import { clsx } from 'clsx';
6
5
  import { twMerge } from 'tailwind-merge';
7
6
  import { Img } from '@page-speed/img';
8
- import useEmblaCarousel from 'embla-carousel-react';
9
- import { jsx, jsxs } from 'react/jsx-runtime';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { AnimatePresence, motion } from 'motion/react';
9
+ import { Pressable, buttonVariants } from '@page-speed/pressable';
10
10
 
11
11
  // components/blocks/hero/hero-tech-carousel.tsx
12
12
  function cn(...inputs) {
13
13
  return twMerge(clsx(inputs));
14
14
  }
15
- var CarouselContext = React.createContext(null);
16
- function useCarousel() {
17
- const context = React.useContext(CarouselContext);
18
- if (!context) {
19
- throw new Error("useCarousel must be used within a <Carousel />");
20
- }
21
- return context;
22
- }
23
- function Carousel({
24
- orientation = "horizontal",
25
- opts,
26
- setApi,
27
- plugins,
28
- className,
29
- children,
30
- ...props
31
- }) {
32
- const [carouselRef, api] = useEmblaCarousel(
33
- {
34
- ...opts,
35
- axis: orientation === "horizontal" ? "x" : "y"
36
- },
37
- plugins
38
- );
39
- const [canScrollPrev, setCanScrollPrev] = React.useState(false);
40
- const [canScrollNext, setCanScrollNext] = React.useState(false);
41
- const onSelect = React.useCallback((api2) => {
42
- if (!api2) return;
43
- setCanScrollPrev(api2.canScrollPrev());
44
- setCanScrollNext(api2.canScrollNext());
45
- }, []);
46
- const scrollPrev = React.useCallback(() => {
47
- api?.scrollPrev();
48
- }, [api]);
49
- const scrollNext = React.useCallback(() => {
50
- api?.scrollNext();
51
- }, [api]);
52
- const handleKeyDown = React.useCallback(
53
- (event) => {
54
- if (event.key === "ArrowLeft") {
55
- event.preventDefault();
56
- scrollPrev();
57
- } else if (event.key === "ArrowRight") {
58
- event.preventDefault();
59
- scrollNext();
60
- }
61
- },
62
- [scrollPrev, scrollNext]
63
- );
64
- React.useEffect(() => {
65
- if (!api || !setApi) return;
66
- setApi(api);
67
- }, [api, setApi]);
68
- React.useEffect(() => {
69
- if (!api) return;
70
- onSelect(api);
71
- api.on("reInit", onSelect);
72
- api.on("select", onSelect);
73
- return () => {
74
- api?.off("select", onSelect);
75
- };
76
- }, [api, onSelect]);
77
- return /* @__PURE__ */ jsx(
78
- CarouselContext.Provider,
79
- {
80
- value: {
81
- carouselRef,
82
- api,
83
- opts,
84
- orientation: orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
85
- scrollPrev,
86
- scrollNext,
87
- canScrollPrev,
88
- canScrollNext
89
- },
90
- children: /* @__PURE__ */ jsx(
91
- "div",
92
- {
93
- onKeyDownCapture: handleKeyDown,
94
- className: cn("relative", className),
95
- role: "region",
96
- "aria-roledescription": "carousel",
97
- "data-slot": "carousel",
98
- ...props,
99
- children
100
- }
101
- )
102
- }
103
- );
104
- }
105
- function CarouselContent({ className, ...props }) {
106
- const { carouselRef, orientation } = useCarousel();
107
- return /* @__PURE__ */ jsx(
108
- "div",
109
- {
110
- ref: carouselRef,
111
- className: "overflow-hidden",
112
- "data-slot": "carousel-content",
113
- children: /* @__PURE__ */ jsx(
114
- "div",
115
- {
116
- className: cn(
117
- "flex",
118
- orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
119
- className
120
- ),
121
- ...props
122
- }
123
- )
124
- }
125
- );
126
- }
127
- function CarouselItem({ className, ...props }) {
128
- const { orientation } = useCarousel();
129
- return /* @__PURE__ */ jsx(
130
- "div",
131
- {
132
- role: "group",
133
- "aria-roledescription": "slide",
134
- "data-slot": "carousel-item",
135
- className: cn(
136
- "min-w-0 shrink-0 grow-0 basis-full",
137
- orientation === "horizontal" ? "pl-4" : "pt-4",
138
- className
139
- ),
140
- ...props
141
- }
142
- );
143
- }
144
15
  var maxWidthStyles = {
145
16
  sm: "max-w-screen-sm",
146
17
  md: "max-w-screen-md",
@@ -150,7 +21,7 @@ var maxWidthStyles = {
150
21
  "4xl": "max-w-[1536px]",
151
22
  full: "max-w-full"
152
23
  };
153
- var Container = React__default.forwardRef(
24
+ var Container = React3__default.forwardRef(
154
25
  ({ children, maxWidth = "xl", className, as = "div", ...props }, ref) => {
155
26
  const Component = as;
156
27
  return /* @__PURE__ */ jsx(
@@ -456,7 +327,7 @@ var spacingStyles = {
456
327
  };
457
328
  var predefinedSpacings = ["none", "sm", "md", "lg", "xl", "hero"];
458
329
  var isPredefinedSpacing = (spacing) => predefinedSpacings.includes(spacing);
459
- var Section = React__default.forwardRef(
330
+ var Section = React3__default.forwardRef(
460
331
  ({
461
332
  id,
462
333
  title,
@@ -517,105 +388,479 @@ var Section = React__default.forwardRef(
517
388
  }
518
389
  );
519
390
  Section.displayName = "Section";
520
- function HeroTechCarousel({
521
- sectionId = "hero-tech-carousel",
522
- heading,
523
- description,
524
- technologies,
525
- carouselSlot,
526
- autoplayDelay = 5e3,
527
- background,
528
- pattern,
529
- patternOpacity,
391
+ var slideVariants = {
392
+ initial: {
393
+ scale: 0.98,
394
+ opacity: 0,
395
+ rotateX: 12
396
+ },
397
+ visible: {
398
+ scale: 1,
399
+ rotateX: 0,
400
+ opacity: 1,
401
+ transition: {
402
+ duration: 0.55,
403
+ ease: [0.645, 0.045, 0.355, 1]
404
+ }
405
+ },
406
+ upExit: {
407
+ opacity: 0.8,
408
+ y: "-120%",
409
+ transition: {
410
+ duration: 0.8
411
+ }
412
+ },
413
+ downExit: {
414
+ opacity: 0.8,
415
+ y: "120%",
416
+ transition: {
417
+ duration: 0.8
418
+ }
419
+ }
420
+ };
421
+ var normalizeIndex = (index, length) => {
422
+ if (!length) return 0;
423
+ const safeIndex = index % length;
424
+ return safeIndex < 0 ? safeIndex + length : safeIndex;
425
+ };
426
+ var ImageSlider = ({
427
+ images,
428
+ children,
429
+ overlay = true,
430
+ overlaySlot,
431
+ overlayClassName,
530
432
  className,
531
- containerClassName = "px-6 sm:px-6 md:px-8 lg:px-8",
532
- spacing = "py-32",
533
- headingClassName,
534
- descriptionClassName,
433
+ imageClassName,
434
+ contentClassName,
435
+ autoplay = true,
436
+ autoplayIntervalMs = 6e3,
437
+ direction = "up",
438
+ transition = "slide",
439
+ startIndex = 0,
440
+ enableKeyboard = true,
441
+ onSlideChange,
535
442
  optixFlowConfig
536
- }) {
537
- const plugin = useRef(
538
- Autoplay({ delay: autoplayDelay, stopOnInteraction: false })
443
+ }) => {
444
+ const hasImages = images.length > 0;
445
+ const [currentIndex, setCurrentIndex] = React3.useState(
446
+ () => normalizeIndex(startIndex, images.length)
539
447
  );
540
- const [api, setApi] = useState();
541
- const [current, setCurrent] = useState(0);
542
- const [fadeIn, setFadeIn] = useState(true);
543
- useEffect(() => {
544
- if (!api) return;
545
- setCurrent(api.selectedScrollSnap());
546
- const updateCurrent = () => {
547
- setFadeIn(false);
548
- setTimeout(() => {
549
- setCurrent(api.selectedScrollSnap());
550
- setFadeIn(true);
551
- }, 200);
448
+ const handleNext = React3.useCallback(() => {
449
+ if (!hasImages) return;
450
+ setCurrentIndex((prevIndex) => {
451
+ const nextIndex = prevIndex + 1 >= images.length ? 0 : prevIndex + 1;
452
+ onSlideChange?.(nextIndex);
453
+ return nextIndex;
454
+ });
455
+ }, [hasImages, images.length, onSlideChange]);
456
+ const handlePrevious = React3.useCallback(() => {
457
+ if (!hasImages) return;
458
+ setCurrentIndex((prevIndex) => {
459
+ const nextIndex = prevIndex - 1 < 0 ? images.length - 1 : prevIndex - 1;
460
+ onSlideChange?.(nextIndex);
461
+ return nextIndex;
462
+ });
463
+ }, [hasImages, images.length, onSlideChange]);
464
+ React3.useEffect(() => {
465
+ if (!hasImages) return;
466
+ setCurrentIndex(normalizeIndex(startIndex, images.length));
467
+ }, [startIndex, images.length, hasImages]);
468
+ React3.useEffect(() => {
469
+ if (!enableKeyboard || !hasImages) return;
470
+ const handleKeyDown = (event) => {
471
+ if (event.key === "ArrowRight") {
472
+ handleNext();
473
+ } else if (event.key === "ArrowLeft") {
474
+ handlePrevious();
475
+ }
552
476
  };
553
- api.on("select", updateCurrent);
554
- api.on("settle", updateCurrent);
477
+ window.addEventListener("keydown", handleKeyDown);
555
478
  return () => {
556
- api.off("select", updateCurrent);
557
- api.off("settle", updateCurrent);
479
+ window.removeEventListener("keydown", handleKeyDown);
558
480
  };
559
- }, [api]);
560
- const selectTechnology = (index) => {
561
- if (api) {
562
- api.scrollTo(index);
481
+ }, [enableKeyboard, handleNext, handlePrevious, hasImages]);
482
+ React3.useEffect(() => {
483
+ if (!autoplay || images.length < 2) return;
484
+ const interval = window.setInterval(handleNext, autoplayIntervalMs);
485
+ return () => window.clearInterval(interval);
486
+ }, [autoplay, autoplayIntervalMs, handleNext, images.length]);
487
+ const activeImage = hasImages ? images[currentIndex] : null;
488
+ const overlayContent = overlaySlot ? /* @__PURE__ */ jsx("div", { className: cn("absolute inset-0 z-10", overlayClassName), children: overlaySlot }) : overlay ? /* @__PURE__ */ jsx(
489
+ "div",
490
+ {
491
+ className: cn(
492
+ "absolute inset-0 z-10 bg-linear-to-b from-black/35 via-black/50 to-black/70",
493
+ overlayClassName
494
+ )
563
495
  }
564
- };
565
- const renderCarousel = useMemo(() => {
566
- if (carouselSlot) return carouselSlot;
567
- if (!technologies || technologies.length === 0) return null;
568
- return /* @__PURE__ */ jsx(
569
- Carousel,
570
- {
571
- setApi,
572
- plugins: [plugin.current],
573
- opts: {
574
- loop: true
575
- },
576
- className: "relative mx-auto w-full max-w-3xl before:absolute before:top-0 before:bottom-0 before:left-0 before:z-10 before:w-36 before:bg-linear-to-r before:from-background before:to-transparent after:absolute after:top-0 after:right-0 after:bottom-0 after:z-10 after:w-36 after:bg-linear-to-l after:from-background after:to-transparent",
577
- onMouseEnter: plugin.current.stop,
578
- onMouseLeave: plugin.current.reset,
579
- children: /* @__PURE__ */ jsx(CarouselContent, { children: technologies.map((technology, idx) => /* @__PURE__ */ jsx(
580
- CarouselItem,
496
+ ) : null;
497
+ return /* @__PURE__ */ jsxs(
498
+ "div",
499
+ {
500
+ className: cn(
501
+ "relative flex min-h-[420px] w-full items-center justify-center overflow-hidden rounded-3xl border border-border/40 bg-muted/30 shadow-2xl",
502
+ className
503
+ ),
504
+ style: {
505
+ perspective: "1000px"
506
+ },
507
+ children: [
508
+ transition === "fade" ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0", children: images.map((image, index) => /* @__PURE__ */ jsx(
509
+ "div",
510
+ {
511
+ "aria-hidden": index !== currentIndex,
512
+ className: cn(
513
+ "absolute inset-0 opacity-0 transition-opacity duration-1000 ease-in-out motion-reduce:transition-none",
514
+ index === currentIndex && "opacity-100"
515
+ ),
516
+ children: /* @__PURE__ */ jsx(
517
+ Img,
518
+ {
519
+ src: image.src,
520
+ alt: image.alt,
521
+ className: cn(
522
+ "h-full w-full object-cover object-center",
523
+ imageClassName,
524
+ image.className
525
+ ),
526
+ optixFlowConfig: image.optixFlowConfig ?? optixFlowConfig,
527
+ loading: "eager"
528
+ }
529
+ )
530
+ },
531
+ `${image.src ?? "image"}-${index}`
532
+ )) }) : /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", initial: false, children: activeImage ? /* @__PURE__ */ jsx(
533
+ motion.div,
581
534
  {
582
- className: "basis-1/3 select-none sm:basis-1/4 md:basis-1/6",
583
- children: /* @__PURE__ */ jsxs(
584
- "div",
535
+ initial: "initial",
536
+ animate: "visible",
537
+ exit: direction === "up" ? "upExit" : "downExit",
538
+ variants: slideVariants,
539
+ className: "absolute inset-0",
540
+ children: /* @__PURE__ */ jsx(
541
+ Img,
585
542
  {
543
+ src: activeImage.src,
544
+ alt: activeImage.alt,
586
545
  className: cn(
587
- "flex cursor-pointer items-center justify-center gap-2 rounded-md border px-4 py-2",
588
- idx === current ? "border-input" : "border-transparent"
546
+ "h-full w-full object-cover object-center",
547
+ imageClassName,
548
+ activeImage.className
589
549
  ),
590
- onClick: () => selectTechnology(idx),
591
- children: [
592
- technology.logo && /* @__PURE__ */ jsx(
593
- Img,
594
- {
595
- className: "h-4 shrink-0 md:h-7",
596
- src: technology.logo,
597
- alt: technology.name,
598
- optixFlowConfig
599
- }
600
- ),
601
- /* @__PURE__ */ jsx("p", { className: "text-nowrap", children: technology.name })
602
- ]
550
+ optixFlowConfig: activeImage.optixFlowConfig ?? optixFlowConfig,
551
+ loading: "eager"
603
552
  }
604
553
  )
605
554
  },
606
- idx
607
- )) })
555
+ `${currentIndex}-${activeImage.src ?? "image"}`
556
+ ) : null }),
557
+ overlayContent,
558
+ children ? /* @__PURE__ */ jsx(
559
+ "div",
560
+ {
561
+ className: cn(
562
+ "relative z-20 flex w-full flex-col items-center justify-center px-6 py-12 text-center text-white md:px-12",
563
+ contentClassName
564
+ ),
565
+ children
566
+ }
567
+ ) : null
568
+ ]
569
+ }
570
+ );
571
+ };
572
+ var MOBILE_CLASSES = {
573
+ "fit-left": "items-start md:items-center",
574
+ "fit-center": "items-center",
575
+ "fit-right": "items-end md:items-center",
576
+ "full-left": "items-stretch md:items-center",
577
+ "full-center": "items-stretch md:items-center",
578
+ "full-right": "items-stretch md:items-center"
579
+ };
580
+ function BlockActions({
581
+ mobileConfig,
582
+ actionsClassName,
583
+ verticalSpacing = "mt-4 md:mt-8",
584
+ actions,
585
+ actionsSlot
586
+ }) {
587
+ const width = mobileConfig?.width ?? "full";
588
+ const position = mobileConfig?.position ?? "center";
589
+ const mobileLayoutClass = MOBILE_CLASSES[`${width}-${position}`];
590
+ if (actionsSlot) {
591
+ return /* @__PURE__ */ jsx("div", { children: actionsSlot });
592
+ } else if (actions && actions?.length > 0) {
593
+ return /* @__PURE__ */ jsx(
594
+ "div",
595
+ {
596
+ className: cn(
597
+ "flex flex-col md:flex-row flex-wrap gap-4",
598
+ mobileLayoutClass,
599
+ actionsClassName,
600
+ verticalSpacing
601
+ ),
602
+ children: actions.map((action, index) => /* @__PURE__ */ jsx(ActionComponent, { action }, index))
608
603
  }
609
604
  );
605
+ } else {
606
+ return null;
607
+ }
608
+ }
609
+ function ActionComponent({ action }) {
610
+ const {
611
+ label,
612
+ icon,
613
+ iconAfter,
614
+ children,
615
+ href,
616
+ onClick,
617
+ asButton,
618
+ variant,
619
+ size,
620
+ className: actionClassName,
621
+ ...pressableProps
622
+ } = action;
623
+ const shouldStyleAsButton = asButton ?? true;
624
+ const resolvedVariant = shouldStyleAsButton ? variant ?? "default" : void 0;
625
+ const resolvedSize = shouldStyleAsButton ? size ?? "default" : void 0;
626
+ return /* @__PURE__ */ jsx(
627
+ Pressable,
628
+ {
629
+ href,
630
+ onClick,
631
+ asButton: shouldStyleAsButton,
632
+ variant: resolvedVariant,
633
+ size: resolvedSize,
634
+ "data-slot": shouldStyleAsButton ? "button" : void 0,
635
+ "data-variant": resolvedVariant,
636
+ "data-size": resolvedSize,
637
+ className: cn(
638
+ shouldStyleAsButton && buttonVariants({ variant: resolvedVariant, size: resolvedSize }),
639
+ actionClassName
640
+ ),
641
+ ...pressableProps,
642
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
643
+ icon,
644
+ label,
645
+ iconAfter
646
+ ] })
647
+ }
648
+ );
649
+ }
650
+ var HERO_TECH_CAROUSEL_MAX_ITEMS = 4;
651
+ function resolveLogoSrc(logo) {
652
+ if (typeof logo.src === "string") return logo.src;
653
+ return logo.src?.light;
654
+ }
655
+ function HeroPanel({
656
+ item,
657
+ defaultAutoplayIntervalMs,
658
+ optixFlowConfig,
659
+ panelContentClassName
660
+ }) {
661
+ const {
662
+ logo,
663
+ logoSlot,
664
+ title,
665
+ content,
666
+ actions,
667
+ backgroundMedia,
668
+ backgroundAutoplayIntervalMs,
669
+ id,
670
+ className,
671
+ contentClassName,
672
+ logoClassName,
673
+ titleClassName,
674
+ textClassName,
675
+ actionsClassName,
676
+ overlayClassName,
677
+ optixFlowConfig: itemOptixFlowConfig
678
+ } = item;
679
+ const resolvedOptixFlow = itemOptixFlowConfig ?? optixFlowConfig;
680
+ const renderBackground = useMemo(() => {
681
+ if (!backgroundMedia || backgroundMedia.length === 0) return null;
682
+ if (backgroundMedia.length === 1) {
683
+ const image = backgroundMedia[0];
684
+ if (!image?.src) return null;
685
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-0", children: [
686
+ /* @__PURE__ */ jsx(
687
+ Img,
688
+ {
689
+ src: image.src,
690
+ alt: image.alt ?? "",
691
+ className: cn(
692
+ "h-full w-full object-cover object-center",
693
+ image.className
694
+ ),
695
+ optixFlowConfig: image.optixFlowConfig ?? resolvedOptixFlow,
696
+ loading: "eager"
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsx(
700
+ "div",
701
+ {
702
+ className: cn(
703
+ "absolute inset-0 bg-linear-to-b from-black/55 via-black/45 to-black/65",
704
+ overlayClassName
705
+ )
706
+ }
707
+ )
708
+ ] });
709
+ }
710
+ return /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 z-0", children: [
711
+ /* @__PURE__ */ jsx(
712
+ ImageSlider,
713
+ {
714
+ images: backgroundMedia,
715
+ className: "h-full w-full rounded-none border-0 shadow-none",
716
+ imageClassName: "object-cover object-center",
717
+ transition: "fade",
718
+ autoplay: true,
719
+ autoplayIntervalMs: backgroundAutoplayIntervalMs ?? defaultAutoplayIntervalMs,
720
+ enableKeyboard: false,
721
+ overlay: false,
722
+ optixFlowConfig: resolvedOptixFlow
723
+ }
724
+ ),
725
+ /* @__PURE__ */ jsx(
726
+ "div",
727
+ {
728
+ className: cn(
729
+ "absolute inset-0 bg-linear-to-b from-black/55 via-black/45 to-black/65",
730
+ overlayClassName
731
+ )
732
+ }
733
+ )
734
+ ] });
610
735
  }, [
611
- carouselSlot,
612
- technologies,
613
- setApi,
614
- plugin,
615
- current,
616
- selectTechnology,
617
- optixFlowConfig
736
+ backgroundMedia,
737
+ backgroundAutoplayIntervalMs,
738
+ defaultAutoplayIntervalMs,
739
+ resolvedOptixFlow,
740
+ overlayClassName
618
741
  ]);
742
+ const hasBackground = !!backgroundMedia && backgroundMedia.length > 0;
743
+ const renderLogo = useMemo(() => {
744
+ if (logoSlot) return logoSlot;
745
+ if (!logo) return null;
746
+ const src = resolveLogoSrc(logo);
747
+ if (!src) return null;
748
+ return /* @__PURE__ */ jsx(
749
+ Img,
750
+ {
751
+ src,
752
+ alt: logo.alt,
753
+ className: cn(
754
+ "h-10 md:h-12 lg:h-14 w-auto max-w-[70%] object-contain",
755
+ logo.imgClassName,
756
+ logoClassName
757
+ ),
758
+ optixFlowConfig: resolvedOptixFlow
759
+ }
760
+ );
761
+ }, [logoSlot, logo, logoClassName, resolvedOptixFlow]);
762
+ const renderTitle = useMemo(() => {
763
+ if (title === void 0 || title === null || title === "") return null;
764
+ if (typeof title === "string") {
765
+ return /* @__PURE__ */ jsx(
766
+ "h2",
767
+ {
768
+ className: cn(
769
+ "text-2xl md:text-3xl lg:text-4xl font-semibold text-balance",
770
+ hasBackground && "text-white text-shadow-lg",
771
+ titleClassName
772
+ ),
773
+ children: title
774
+ }
775
+ );
776
+ }
777
+ return /* @__PURE__ */ jsx("div", { className: titleClassName, children: title });
778
+ }, [title, titleClassName, hasBackground]);
779
+ const renderContent = useMemo(() => {
780
+ if (content === void 0 || content === null || content === "")
781
+ return null;
782
+ if (typeof content === "string") {
783
+ return /* @__PURE__ */ jsx(
784
+ "p",
785
+ {
786
+ className: cn(
787
+ "text-sm md:text-base leading-snug text-balance",
788
+ hasBackground ? "text-white text-shadow-lg" : "",
789
+ textClassName
790
+ ),
791
+ children: content
792
+ }
793
+ );
794
+ }
795
+ return /* @__PURE__ */ jsx("div", { className: textClassName, children: content });
796
+ }, [content, textClassName, hasBackground]);
797
+ return /* @__PURE__ */ jsxs(
798
+ "div",
799
+ {
800
+ id,
801
+ "data-slot": "hero-tech-carousel-panel",
802
+ className: cn(
803
+ // Mobile: stack vertically with content-fit height + padding.
804
+ "relative w-full overflow-hidden",
805
+ // Desktop: flex children share the row equally and fill height.
806
+ "md:h-full md:flex-1 md:basis-0 md:min-w-0",
807
+ // Provide a default panel background when no media is supplied so
808
+ // separators between panels remain visible.
809
+ !hasBackground && "bg-muted/30",
810
+ className
811
+ ),
812
+ children: [
813
+ renderBackground,
814
+ /* @__PURE__ */ jsxs(
815
+ "div",
816
+ {
817
+ className: cn(
818
+ "relative z-10 flex h-full w-full flex-col items-center justify-center gap-4 md:gap-6",
819
+ // Mobile padding keeps content readable when stacked.
820
+ "px-6 py-12 md:px-8 md:py-12 lg:px-10",
821
+ // Center content vertically; on desktop columns can be quite tall.
822
+ "text-center",
823
+ panelContentClassName,
824
+ contentClassName
825
+ ),
826
+ children: [
827
+ renderLogo,
828
+ renderTitle,
829
+ renderContent,
830
+ /* @__PURE__ */ jsx(
831
+ BlockActions,
832
+ {
833
+ actions,
834
+ actionsClassName,
835
+ verticalSpacing: "mt-2 md:mt-4",
836
+ mobileConfig: { width: "fit", position: "center" }
837
+ }
838
+ )
839
+ ]
840
+ }
841
+ )
842
+ ]
843
+ }
844
+ );
845
+ }
846
+ function HeroTechCarousel({
847
+ sectionId = "hero-tech-carousel",
848
+ items,
849
+ backgroundAutoplayIntervalMs = 5e3,
850
+ background,
851
+ spacing = "none",
852
+ pattern,
853
+ patternOpacity,
854
+ className,
855
+ containerClassName = "px-0 sm:px-0 lg:px-0 max-w-full relative z-10 h-auto md:h-dvh w-screen flex items-stretch",
856
+ panelsClassName,
857
+ panelContentClassName,
858
+ optixFlowConfig
859
+ }) {
860
+ const visibleItems = useMemo(() => {
861
+ if (!items || items.length === 0) return [];
862
+ return items.slice(0, HERO_TECH_CAROUSEL_MAX_ITEMS);
863
+ }, [items]);
619
864
  return /* @__PURE__ */ jsx(
620
865
  Section,
621
866
  {
@@ -624,80 +869,37 @@ function HeroTechCarousel({
624
869
  spacing,
625
870
  pattern,
626
871
  patternOpacity,
627
- className,
872
+ className: cn(
873
+ "relative w-screen overflow-hidden",
874
+ // Desktop fills the viewport; on mobile content drives the height.
875
+ "h-auto md:h-dvh",
876
+ "px-0 pt-0 pb-0",
877
+ className
878
+ ),
628
879
  containerClassName,
629
- children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
630
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-center", children: [
631
- heading && (typeof heading === "string" ? /* @__PURE__ */ jsx(
632
- "h1",
633
- {
634
- className: cn(
635
- "mx-auto mb-4 max-w-2xl text-center text-4xl font-bold md:text-6xl text-balance",
636
- headingClassName
637
- ),
638
- children: heading
639
- }
640
- ) : /* @__PURE__ */ jsx(
641
- "h1",
642
- {
643
- className: cn(
644
- "mx-auto mb-4 max-w-2xl text-center text-4xl font-bold md:text-6xl text-balance",
645
- headingClassName
646
- ),
647
- children: heading
648
- }
649
- )),
650
- description && (typeof description === "string" ? /* @__PURE__ */ jsx(
651
- "p",
652
- {
653
- className: cn(
654
- "mx-auto mt-4 max-w-xl text-center text-lg text-balance",
655
- descriptionClassName
656
- ),
657
- children: description
658
- }
659
- ) : /* @__PURE__ */ jsx("div", { className: descriptionClassName, children: description })),
660
- technologies && technologies.length > 0 && /* @__PURE__ */ jsx(
661
- "div",
880
+ children: visibleItems.length === 0 ? null : /* @__PURE__ */ jsx(
881
+ "div",
882
+ {
883
+ "data-slot": "hero-tech-carousel-panels",
884
+ className: cn(
885
+ // Mobile: vertical stack with auto height per panel.
886
+ "flex w-full flex-col",
887
+ // Desktop: equal-width row that fills section height.
888
+ "md:h-full md:flex-row md:items-stretch",
889
+ panelsClassName
890
+ ),
891
+ children: visibleItems.map((item, idx) => /* @__PURE__ */ jsx(
892
+ HeroPanel,
662
893
  {
663
- className: cn(
664
- "mx-auto bg-muted mt-8 mb-12 flex h-[60px] w-fit items-center gap-2 rounded-md px-4 py-2 text-center"
665
- ),
666
- children: /* @__PURE__ */ jsxs(
667
- "div",
668
- {
669
- className: cn(
670
- "flex items-center gap-2 transition-opacity duration-300",
671
- fadeIn ? "opacity-100" : "opacity-0"
672
- ),
673
- children: [
674
- technologies && current && technologies[current] && technologies[current].logo ? /* @__PURE__ */ jsx(
675
- Img,
676
- {
677
- src: technologies[current]?.logo,
678
- alt: technologies[current]?.name,
679
- className: "h-4 md:h-7",
680
- optixFlowConfig
681
- }
682
- ) : null,
683
- /* @__PURE__ */ jsx(
684
- "p",
685
- {
686
- className: cn(
687
- "px-2 font-mono text-sm",
688
- technologies && technologies[current] && technologies[current].logo ? "border-l" : ""
689
- ),
690
- children: technologies[current]?.command
691
- }
692
- )
693
- ]
694
- }
695
- )
696
- }
697
- )
698
- ] }),
699
- renderCarousel
700
- ] })
894
+ item,
895
+ defaultAutoplayIntervalMs: backgroundAutoplayIntervalMs,
896
+ optixFlowConfig,
897
+ panelContentClassName
898
+ },
899
+ item.id ?? idx
900
+ ))
901
+ }
902
+ )
701
903
  }
702
904
  );
703
905
  }