fancy-ui-react 0.3.0 → 0.4.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.
Files changed (86) hide show
  1. package/README.md +52 -19
  2. package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
  3. package/dist/components/animated-beam/AnimatedBeam.js +293 -118
  4. package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
  5. package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
  6. package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
  7. package/dist/components/bento-grid/BentoFrame.js +28 -0
  8. package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
  9. package/dist/components/bento-grid/BentoGrid.js +71 -2
  10. package/dist/components/bento-grid/BentoGridCard.js +34 -26
  11. package/dist/components/bento-grid/BentoGridItem.js +10 -10
  12. package/dist/components/border-beam/BorderBeam.d.ts +55 -10
  13. package/dist/components/border-beam/BorderBeam.js +93 -16
  14. package/dist/components/compare/Compare.d.ts +32 -8
  15. package/dist/components/compare/Compare.js +213 -95
  16. package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
  17. package/dist/components/container-scroll/ContainerScroll.js +88 -49
  18. package/dist/components/dock/Dock.d.ts +10 -7
  19. package/dist/components/dock/Dock.js +76 -31
  20. package/dist/components/dock/DockIcon.d.ts +14 -3
  21. package/dist/components/dock/DockIcon.js +23 -19
  22. package/dist/components/dock/DockSeparator.d.ts +4 -1
  23. package/dist/components/dock/DockSeparator.js +5 -2
  24. package/dist/components/dock/types.d.ts +13 -6
  25. package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
  26. package/dist/components/editorial-engine/engine.js +1 -14
  27. package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
  28. package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
  29. package/dist/components/flip-card/FlipCard.d.ts +42 -1
  30. package/dist/components/flip-card/FlipCard.js +178 -25
  31. package/dist/components/flip-card/index.d.ts +1 -1
  32. package/dist/components/fluid-cursor/FluidCursor.js +5 -4
  33. package/dist/components/fluid-cursor/dev.d.ts +1 -0
  34. package/dist/components/fluid-cursor/dev.js +8 -0
  35. package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
  36. package/dist/components/fluid-cursor/fluid-shared.js +0 -2
  37. package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
  38. package/dist/components/glow-border/GlowBorder.d.ts +41 -1
  39. package/dist/components/glow-border/GlowBorder.js +96 -16
  40. package/dist/components/glow-border/index.d.ts +2 -2
  41. package/dist/components/gradient-button/GradientButton.d.ts +10 -5
  42. package/dist/components/gradient-button/GradientButton.js +25 -18
  43. package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
  44. package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
  45. package/dist/components/image-trail-cursor/trail-variants.js +331 -8
  46. package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
  47. package/dist/components/marquee/Marquee.d.ts +7 -3
  48. package/dist/components/marquee/Marquee.js +134 -2
  49. package/dist/components/marquee/ReviewCard.d.ts +2 -1
  50. package/dist/components/marquee/ReviewCard.js +20 -9
  51. package/dist/components/meteors/Meteors.d.ts +32 -10
  52. package/dist/components/meteors/Meteors.js +53 -20
  53. package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
  54. package/dist/components/neon-border/NeonBorder.d.ts +24 -5
  55. package/dist/components/neon-border/NeonBorder.js +37 -7
  56. package/dist/components/pagination/Pagination.js +220 -82
  57. package/dist/components/rainbow-button/RainbowButton.js +13 -5
  58. package/dist/components/ripple-button/RippleButton.d.ts +20 -1
  59. package/dist/components/ripple-button/RippleButton.js +44 -21
  60. package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
  61. package/dist/components/shimmer-button/ShimmerButton.js +17 -19
  62. package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
  63. package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
  64. package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
  65. package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
  66. package/dist/components/stepper/Step.js +32 -19
  67. package/dist/components/stepper/Stepper.js +18 -2
  68. package/dist/components/stepper/types.d.ts +13 -0
  69. package/dist/components/timeline/Timeline.d.ts +8 -1
  70. package/dist/components/timeline/Timeline.js +165 -76
  71. package/dist/index.js +2 -1
  72. package/dist/internals/markdown.d.ts +5 -0
  73. package/dist/internals/motion/haptics.d.ts +0 -4
  74. package/dist/internals/motion/presence-core.d.ts +59 -0
  75. package/dist/internals/motion/presence-core.js +162 -0
  76. package/dist/internals/motion/presence.d.ts +2 -4
  77. package/dist/internals/motion/presence.js +1 -156
  78. package/dist/internals/motion/raf.d.ts +1 -1
  79. package/dist/internals/motion/stagger.d.ts +3 -4
  80. package/dist/internals/motion/tokens.js +4 -0
  81. package/dist/internals/motion/transitions.d.ts +17 -17
  82. package/dist/internals/motion/types.d.ts +6 -7
  83. package/dist/internals/relative-time.d.ts +1 -1
  84. package/dist/styles.css +3470 -203
  85. package/package.json +18 -2
  86. package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
@@ -37,12 +37,12 @@ class ImageItem {
37
37
  el: null,
38
38
  inner: null
39
39
  };
40
- // Port divergence: `private` where the Svelte source says `public`. gsap declares
41
- // `TweenVars` inside a GLOBAL `namespace gsap` with no module-level export, so a
42
- // public `gsap.TweenVars` member emits a bare `gsap.` reference into the shipped
43
- // .d.ts while tsc drops the value-only `import { gsap }` — consumers then hit
44
- // TS2503 "Cannot find namespace 'gsap'". A private member emits no type at all.
45
- // Read only by initEvents() below, so nothing outside this class loses access.
40
+ // `private`, not `public`: the animation library declares `TweenVars` inside a
41
+ // GLOBAL namespace with no module-level export, so a public `gsap.TweenVars`
42
+ // member emits a bare `gsap.` reference into the shipped .d.ts while tsc drops
43
+ // the value-only `import { gsap }` — deep-importing consumers then hit TS2503
44
+ // "Cannot find namespace 'gsap'". A private member emits no type at all, and it
45
+ // is read only by initEvents() below, so nothing outside this class loses access.
46
46
  defaultStyle = { scale: 1, x: 0, y: 0, opacity: 0 };
47
47
  rect = null;
48
48
  resizeHandler = null;
@@ -164,7 +164,8 @@ class BaseVariant {
164
164
  const now = performance.now();
165
165
  this.positionHistory.push({ x, y, t: now });
166
166
  const cutoff = now - BaseVariant.HISTORY_MAX_AGE;
167
- while (this.positionHistory.length > BaseVariant.HISTORY_MAX_LEN || this.positionHistory.length > 1 && this.positionHistory[0].t < cutoff) {
167
+ while (this.positionHistory.length > BaseVariant.HISTORY_MAX_LEN || // Non-null: `length > 1` on this same clause proves index 0 exists.
168
+ this.positionHistory.length > 1 && this.positionHistory[0].t < cutoff) {
168
169
  this.positionHistory.shift();
169
170
  }
170
171
  }
@@ -203,6 +204,11 @@ class BaseVariant {
203
204
  this.cacheMousePos.x = lerp(this.cacheMousePos.x, this.mousePos.x, 0.1);
204
205
  this.cacheMousePos.y = lerp(this.cacheMousePos.y, this.mousePos.y, 0.1);
205
206
  }
207
+ /** Advance to the next image in the ring. */
208
+ nextImage() {
209
+ this.imgPosition = this.imgPosition < this.imagesTotal - 1 ? this.imgPosition + 1 : 0;
210
+ return this.images[this.imgPosition];
211
+ }
206
212
  onImageActivated() {
207
213
  this.activeImagesCount++;
208
214
  this.isIdle = false;
@@ -761,6 +767,308 @@ class ImageTrailVariantPixelated extends BaseVariant {
761
767
  ).set(img.DOM.el, { opacity: 0, scale: 0.2 }, ImageTrailVariantPixelated.HOLD);
762
768
  }
763
769
  }
770
+ function randomBetween(min, max) {
771
+ return min + Math.random() * (max - min);
772
+ }
773
+ class ImageTrailVariantScale extends BaseVariant {
774
+ showNextImage() {
775
+ ++this.zIndexVal;
776
+ const img = this.nextImage();
777
+ if (!img) return;
778
+ const w = img.rect?.width ?? 0;
779
+ const h = img.rect?.height ?? 0;
780
+ gsap.killTweensOf(img.DOM.el);
781
+ gsap.timeline({
782
+ onStart: () => this.onImageActivated(),
783
+ onComplete: () => this.onImageDeactivated()
784
+ }).set(img.DOM.el, {
785
+ opacity: 1,
786
+ scale: 0,
787
+ rotation: randomBetween(-20, 20),
788
+ zIndex: this.zIndexVal,
789
+ x: this.mousePos.x - w / 2,
790
+ y: this.mousePos.y - h / 2
791
+ }).to(img.DOM.el, { scale: 1, duration: 0.6, ease: "back.out(2)" }, 0).to(img.DOM.el, { scale: 0, duration: 0.6, ease: "power2.in" }, 0.75).set(img.DOM.el, { opacity: 0 });
792
+ }
793
+ }
794
+ class ImageTrailVariantFall extends BaseVariant {
795
+ showNextImage() {
796
+ ++this.zIndexVal;
797
+ const img = this.nextImage();
798
+ if (!img) return;
799
+ const w = img.rect?.width ?? 0;
800
+ const h = img.rect?.height ?? 0;
801
+ const floor = this.container.getBoundingClientRect().height;
802
+ gsap.killTweensOf(img.DOM.el);
803
+ gsap.timeline({
804
+ onStart: () => this.onImageActivated(),
805
+ onComplete: () => this.onImageDeactivated()
806
+ }).set(img.DOM.el, {
807
+ opacity: 1,
808
+ scale: 0,
809
+ rotation: 0,
810
+ zIndex: this.zIndexVal,
811
+ x: this.mousePos.x - w / 2,
812
+ y: this.mousePos.y - h / 2
813
+ }).to(img.DOM.el, { scale: 1, duration: 0.8, ease: "elastic.out(1, 0.3)" }, 0).to(img.DOM.el, { rotation: randomBetween(-360, 360), duration: 1.1, ease: "power1.out" }, 0).to(img.DOM.el, { y: floor * 1.2 + h, duration: 1, ease: "back.in(0.4)" }, 0).set(img.DOM.el, { opacity: 0 });
814
+ }
815
+ }
816
+ class ImageTrailVariantGravity extends BaseVariant {
817
+ showNextImage() {
818
+ ++this.zIndexVal;
819
+ const img = this.nextImage();
820
+ if (!img) return;
821
+ const w = img.rect?.width ?? 0;
822
+ const h = img.rect?.height ?? 0;
823
+ const height = this.container.getBoundingClientRect().height;
824
+ const startY = this.mousePos.y - h / 2;
825
+ const floorY = Math.max(startY, height - h);
826
+ const drop = floorY - startY;
827
+ const push = Math.max(-140, Math.min(140, (this.mousePos.x - this.lastMousePos.x) * 1.4));
828
+ const fall = 0.3 + 0.35 * Math.sqrt(drop / Math.max(1, height));
829
+ const bounce = drop * 0.32;
830
+ gsap.killTweensOf(img.DOM.el);
831
+ const el = img.DOM.el;
832
+ gsap.timeline({
833
+ onStart: () => this.onImageActivated(),
834
+ onComplete: () => this.onImageDeactivated()
835
+ }).set(el, {
836
+ opacity: 1,
837
+ scale: 0,
838
+ rotation: randomBetween(-10, 10),
839
+ zIndex: this.zIndexVal,
840
+ x: this.mousePos.x - w / 2,
841
+ y: startY
842
+ }).to(el, { scale: 1, duration: 0.4, ease: "elastic.out(2, 0.6)" }, 0).to(
843
+ el,
844
+ {
845
+ x: `+=${push}`,
846
+ rotation: randomBetween(-30, 30),
847
+ duration: fall + 0.9,
848
+ ease: "power1.out"
849
+ },
850
+ 0.1
851
+ ).to(el, { y: floorY, duration: fall, ease: "power2.in" }, 0.15).to(el, { y: floorY - bounce, duration: 0.28, ease: "power2.out" }).to(el, { y: floorY, duration: 0.28, ease: "power2.in" }).to(el, { y: floorY - bounce * 0.3, duration: 0.16, ease: "power2.out" }).to(el, { y: floorY, duration: 0.16, ease: "power2.in" }).to(el, { opacity: 0, scale: 0.7, duration: 0.3, ease: "power2.in" }, "+=0.15");
852
+ }
853
+ }
854
+ class ImageTrailVariantFlame extends BaseVariant {
855
+ showNextImage() {
856
+ ++this.zIndexVal;
857
+ const img = this.nextImage();
858
+ if (!img) return;
859
+ const w = img.rect?.width ?? 0;
860
+ const h = img.rect?.height ?? 0;
861
+ const { dx, dy } = this.getTouchVelocity();
862
+ const speed = Math.min(1, Math.hypot(dx, dy) / 160);
863
+ const rotation = (Math.random() - 0.5) * 30 * (1 + speed * 2);
864
+ gsap.killTweensOf(img.DOM.el);
865
+ gsap.timeline({
866
+ onStart: () => this.onImageActivated(),
867
+ onComplete: () => this.onImageDeactivated()
868
+ }).set(img.DOM.el, {
869
+ opacity: 1,
870
+ scale: 0,
871
+ rotation,
872
+ zIndex: this.zIndexVal,
873
+ x: this.mousePos.x - w / 2,
874
+ y: this.mousePos.y - h / 2
875
+ }).to(img.DOM.el, { scale: 1, duration: 0.35, ease: "back.out(1.6)" }, 0).to(
876
+ img.DOM.el,
877
+ {
878
+ y: `-=${40 + speed * 40}`,
879
+ scale: 0,
880
+ opacity: 0,
881
+ rotation: rotation * 1.6,
882
+ duration: 0.55,
883
+ ease: "power2.in"
884
+ },
885
+ 0.55
886
+ );
887
+ }
888
+ }
889
+ const SEAM = 0.6;
890
+ const lo = (n) => Math.max(0, n - SEAM);
891
+ const hi = (n) => Math.min(100, n + SEAM);
892
+ const pc = (n) => `${Math.round(n * 100) / 100}%`;
893
+ const poly = (pts) => `polygon(${pts.map(([x, y]) => `${pc(x)} ${pc(y)}`).join(", ")})`;
894
+ function revealFragments(pattern) {
895
+ switch (pattern) {
896
+ case "venetian": {
897
+ const n = 6;
898
+ return Array.from({ length: n }, (_, i) => {
899
+ const y0 = lo(i * 100 / n);
900
+ const y1 = hi((i + 1) * 100 / n);
901
+ return {
902
+ closed: poly([
903
+ [0, y0],
904
+ [100, y0],
905
+ [100, y0],
906
+ [0, y0]
907
+ ]),
908
+ open: poly([
909
+ [0, y0],
910
+ [100, y0],
911
+ [100, y1],
912
+ [0, y1]
913
+ ]),
914
+ order: i / (n - 1)
915
+ };
916
+ });
917
+ }
918
+ case "curtain": {
919
+ const n = 6;
920
+ const mid = (n - 1) / 2;
921
+ return Array.from({ length: n }, (_, i) => {
922
+ const x0 = lo(i * 100 / n);
923
+ const x1 = hi((i + 1) * 100 / n);
924
+ const cx = (x0 + x1) / 2;
925
+ return {
926
+ closed: poly([
927
+ [cx, 0],
928
+ [cx, 0],
929
+ [cx, 100],
930
+ [cx, 100]
931
+ ]),
932
+ open: poly([
933
+ [x0, 0],
934
+ [x1, 0],
935
+ [x1, 100],
936
+ [x0, 100]
937
+ ]),
938
+ order: Math.abs(i - mid) / mid
939
+ };
940
+ });
941
+ }
942
+ case "hexagon": {
943
+ const centres = [16.67, 50, 83.33];
944
+ const r = 31;
945
+ const cells = [];
946
+ for (const cy of centres) {
947
+ for (const cx of centres) {
948
+ const hex = Array.from({ length: 6 }, (_, k) => {
949
+ const a = Math.PI / 3 * k + Math.PI / 6;
950
+ return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
951
+ });
952
+ cells.push({
953
+ closed: poly(hex.map(() => [cx, cy])),
954
+ open: poly(hex),
955
+ order: Math.hypot(cx - 50, cy - 50) / Math.hypot(33.33, 33.33)
956
+ });
957
+ }
958
+ }
959
+ return cells;
960
+ }
961
+ case "liquid": {
962
+ const blobs = [
963
+ [50, 50, 48],
964
+ [22, 26, 36],
965
+ [80, 24, 36],
966
+ [26, 80, 36],
967
+ [78, 78, 38]
968
+ ];
969
+ return blobs.map(([x, y, r], i) => ({
970
+ closed: `circle(0% at ${pc(x)} ${pc(y)})`,
971
+ open: `circle(${pc(r)} at ${pc(x)} ${pc(y)})`,
972
+ order: i / (blobs.length - 1)
973
+ }));
974
+ }
975
+ case "zoom-split": {
976
+ const quads = [
977
+ [0, 0],
978
+ [50, 0],
979
+ [0, 50],
980
+ [50, 50]
981
+ ];
982
+ return quads.map(([x, y], i) => ({
983
+ closed: poly([
984
+ [50, 50],
985
+ [50, 50],
986
+ [50, 50],
987
+ [50, 50]
988
+ ]),
989
+ open: poly([
990
+ [lo(x), lo(y)],
991
+ [hi(x + 50), lo(y)],
992
+ [hi(x + 50), hi(y + 50)],
993
+ [lo(x), hi(y + 50)]
994
+ ]),
995
+ order: i / 3
996
+ }));
997
+ }
998
+ }
999
+ }
1000
+ const FRAG_CLASS = "content__img-frag";
1001
+ class ImageTrailVariantReveal extends BaseVariant {
1002
+ static STAGGER = 0.22;
1003
+ // seconds from first to last fragment
1004
+ fragments;
1005
+ constructor(container, pattern) {
1006
+ super(container);
1007
+ this.fragments = revealFragments(pattern);
1008
+ for (const img of this.images) {
1009
+ const bg = img.DOM.inner?.style.backgroundImage ?? "";
1010
+ for (const f of this.fragments) {
1011
+ const frag = document.createElement("div");
1012
+ frag.className = FRAG_CLASS;
1013
+ frag.style.cssText = `position:absolute;inset:0;background-image:${bg};background-size:cover;background-position:center;clip-path:${f.closed};`;
1014
+ img.DOM.el.appendChild(frag);
1015
+ }
1016
+ if (img.DOM.inner) img.DOM.inner.style.opacity = "0";
1017
+ }
1018
+ }
1019
+ showNextImage() {
1020
+ ++this.zIndexVal;
1021
+ const img = this.nextImage();
1022
+ if (!img) return;
1023
+ const w = img.rect?.width ?? 0;
1024
+ const h = img.rect?.height ?? 0;
1025
+ const frags = [...img.DOM.el.querySelectorAll(`.${FRAG_CLASS}`)];
1026
+ const stagger = ImageTrailVariantReveal.STAGGER;
1027
+ gsap.killTweensOf([img.DOM.el, ...frags]);
1028
+ const tl = gsap.timeline({
1029
+ onStart: () => this.onImageActivated(),
1030
+ onComplete: () => this.onImageDeactivated()
1031
+ }).set(img.DOM.el, {
1032
+ opacity: 1,
1033
+ scale: 0.85,
1034
+ rotation: 0,
1035
+ zIndex: this.zIndexVal,
1036
+ x: this.mousePos.x - w / 2,
1037
+ y: this.mousePos.y - h / 2
1038
+ }).to(img.DOM.el, { scale: 1, duration: 0.5, ease: "power3.out" }, 0);
1039
+ frags.forEach((frag, i) => {
1040
+ const f = this.fragments[i];
1041
+ tl.fromTo(
1042
+ frag,
1043
+ { clipPath: f.closed },
1044
+ { clipPath: f.open, duration: 0.45, ease: "power3.out" },
1045
+ f.order * stagger
1046
+ );
1047
+ tl.to(
1048
+ frag,
1049
+ { clipPath: f.closed, duration: 0.35, ease: "power3.in" },
1050
+ 0.95 + (1 - f.order) * stagger
1051
+ );
1052
+ });
1053
+ tl.set(img.DOM.el, { opacity: 0 });
1054
+ }
1055
+ destroy() {
1056
+ for (const img of this.images) {
1057
+ const frags = img.DOM.el.querySelectorAll(`.${FRAG_CLASS}`);
1058
+ gsap.killTweensOf([...frags]);
1059
+ frags.forEach((f) => f.remove());
1060
+ if (img.DOM.inner) img.DOM.inner.style.opacity = "";
1061
+ }
1062
+ super.destroy();
1063
+ }
1064
+ }
1065
+ function revealVariant(pattern) {
1066
+ return class extends ImageTrailVariantReveal {
1067
+ constructor(container) {
1068
+ super(container, pattern);
1069
+ }
1070
+ };
1071
+ }
764
1072
  const variantMap = {
765
1073
  type1: ImageTrailVariant1,
766
1074
  type2: ImageTrailVariant2,
@@ -770,7 +1078,16 @@ const variantMap = {
770
1078
  type6: ImageTrailVariant6,
771
1079
  type7: ImageTrailVariant7,
772
1080
  type8: ImageTrailVariant8,
773
- pixelated: ImageTrailVariantPixelated
1081
+ pixelated: ImageTrailVariantPixelated,
1082
+ scale: ImageTrailVariantScale,
1083
+ fall: ImageTrailVariantFall,
1084
+ gravity: ImageTrailVariantGravity,
1085
+ flame: ImageTrailVariantFlame,
1086
+ venetian: revealVariant("venetian"),
1087
+ curtain: revealVariant("curtain"),
1088
+ hexagon: revealVariant("hexagon"),
1089
+ liquid: revealVariant("liquid"),
1090
+ "zoom-split": revealVariant("zoom-split")
774
1091
  };
775
1092
  export {
776
1093
  ImageItem,
@@ -782,6 +1099,12 @@ export {
782
1099
  ImageTrailVariant6,
783
1100
  ImageTrailVariant7,
784
1101
  ImageTrailVariant8,
1102
+ ImageTrailVariantFall,
1103
+ ImageTrailVariantFlame,
1104
+ ImageTrailVariantGravity,
785
1105
  ImageTrailVariantPixelated,
1106
+ ImageTrailVariantReveal,
1107
+ ImageTrailVariantScale,
1108
+ revealFragments,
786
1109
  variantMap
787
1110
  };
@@ -3,12 +3,13 @@ import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import { forwardRef } from "react";
4
4
  import { cn } from "../../utils.js";
5
5
  import { useSoundCue } from "../../sound/use-sound.js";
6
+ /* empty css */
6
7
  const InteractiveHoverButton = forwardRef(
7
- ({ text = "Button", className, children, sound = false, onClick, disabled, ...restProps }, ref) => {
8
+ ({ text = "Button", className, children, onClick, sound = false, ...restProps }, ref) => {
8
9
  const label = children ?? text;
9
10
  const playCue = useSoundCue(sound);
10
11
  function handleClick(event) {
11
- if (sound && !disabled) playCue("press");
12
+ if (sound && !restProps.disabled) playCue("press");
12
13
  onClick?.(event);
13
14
  }
14
15
  return /* @__PURE__ */ jsxs(
@@ -16,45 +17,39 @@ const InteractiveHoverButton = forwardRef(
16
17
  {
17
18
  ref,
18
19
  className: cn(
19
- "group bg-background relative w-auto cursor-pointer overflow-hidden rounded-full border p-2 px-6 text-center font-semibold",
20
+ "ihb group bg-background relative isolate w-auto cursor-pointer overflow-hidden rounded-full border px-6 py-2.5 text-center font-semibold",
20
21
  className
21
22
  ),
22
23
  onClick: handleClick,
23
- disabled,
24
24
  ...restProps,
25
25
  children: [
26
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
27
- /* @__PURE__ */ jsx("div", { className: "bg-primary size-2 rounded-lg group-hover:scale-[100.8] motion-safe:transition-all motion-safe:duration-300" }),
28
- /* @__PURE__ */ jsx("span", { className: "inline-block group-hover:translate-x-12 group-hover:opacity-0 motion-safe:transition-all motion-safe:duration-300", children: label })
26
+ /* @__PURE__ */ jsx("span", { className: "ihb-fill", "aria-hidden": "true" }),
27
+ /* @__PURE__ */ jsxs("span", { className: "ihb-rest", children: [
28
+ /* @__PURE__ */ jsx("span", { className: "ihb-dot-space", "aria-hidden": "true" }),
29
+ /* @__PURE__ */ jsx("span", { className: "ihb-label", children: label })
29
30
  ] }),
30
- /* @__PURE__ */ jsxs(
31
- "div",
32
- {
33
- "aria-hidden": "true",
34
- className: "text-primary-foreground absolute top-0 z-10 flex size-full translate-x-12 items-center justify-center gap-2 opacity-0 group-hover:-translate-x-5 group-hover:opacity-100 motion-safe:transition-all motion-safe:duration-300",
35
- children: [
36
- /* @__PURE__ */ jsx("span", { children: label }),
37
- /* @__PURE__ */ jsxs(
38
- "svg",
39
- {
40
- xmlns: "http://www.w3.org/2000/svg",
41
- width: "24",
42
- height: "24",
43
- viewBox: "0 0 24 24",
44
- fill: "none",
45
- stroke: "currentColor",
46
- strokeWidth: "2",
47
- strokeLinecap: "round",
48
- strokeLinejoin: "round",
49
- children: [
50
- /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
51
- /* @__PURE__ */ jsx("path", { d: "m12 5 7 7-7 7" })
52
- ]
53
- }
54
- )
55
- ]
56
- }
57
- )
31
+ /* @__PURE__ */ jsxs("span", { "aria-hidden": "true", className: "ihb-hover", children: [
32
+ /* @__PURE__ */ jsx("span", { className: "ihb-hover-label", children: label }),
33
+ /* @__PURE__ */ jsxs(
34
+ "svg",
35
+ {
36
+ className: "ihb-arrow",
37
+ xmlns: "http://www.w3.org/2000/svg",
38
+ width: "18",
39
+ height: "18",
40
+ viewBox: "0 0 24 24",
41
+ fill: "none",
42
+ stroke: "currentColor",
43
+ strokeWidth: "2",
44
+ strokeLinecap: "round",
45
+ strokeLinejoin: "round",
46
+ children: [
47
+ /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
48
+ /* @__PURE__ */ jsx("path", { d: "m12 5 7 7-7 7" })
49
+ ]
50
+ }
51
+ )
52
+ ] })
58
53
  ]
59
54
  }
60
55
  );
@@ -4,13 +4,17 @@ export interface MarqueeProps {
4
4
  className?: string;
5
5
  /** Reverse the scroll direction */
6
6
  reverse?: boolean;
7
- /** Pause the animation on hover */
7
+ /** Ease the conveyor to a stop while the pointer (or focus) is inside */
8
8
  pauseOnHover?: boolean;
9
9
  /** Scroll vertically instead of horizontally */
10
10
  vertical?: boolean;
11
- /** Number of times to repeat the children track */
11
+ /** Number of copies of the children rendered on the track */
12
12
  repeat?: number;
13
+ /** Dissolve items at both edges with a gradient mask */
14
+ fade?: boolean;
15
+ /** Speed multiplier applied on top of `--duration` (2 = twice as fast) */
16
+ speed?: number;
13
17
  /** Content to repeat and scroll */
14
18
  children?: ReactNode;
15
19
  }
16
- export declare function Marquee({ className, reverse, pauseOnHover, vertical, repeat, children, }: MarqueeProps): import("react").JSX.Element;
20
+ export declare function Marquee({ className, reverse, pauseOnHover, vertical, repeat, fade, speed, children, }: MarqueeProps): import("react").JSX.Element;
@@ -1,26 +1,158 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import { useRef, useState, useEffect } from "react";
3
4
  import { cn } from "../../utils.js";
5
+ import { useReducedMotion } from "../../internals/motion/media-query.js";
4
6
  /* empty css */
7
+ const EASE_TAU = 140;
8
+ const SETTLE_EPSILON = 2e-3;
9
+ const DEFAULT_DURATION_MS = 4e4;
10
+ function parseDuration(raw) {
11
+ const match = /^\s*([\d.]+)\s*(ms|s)\s*$/i.exec(raw);
12
+ if (!match) return DEFAULT_DURATION_MS;
13
+ const value = Number.parseFloat(match[1] ?? "");
14
+ if (!Number.isFinite(value) || value <= 0) return DEFAULT_DURATION_MS;
15
+ return (match[2] ?? "s").toLowerCase() === "ms" ? value : value * 1e3;
16
+ }
17
+ function createConveyor() {
18
+ const c = {
19
+ speed: 1,
20
+ pauseOnHover: false,
21
+ engaged: false,
22
+ factor: 1,
23
+ animations: [],
24
+ rafId: 0,
25
+ lastTs: 0,
26
+ resumeAt: null,
27
+ applyRate() {
28
+ const rate = c.speed * c.factor;
29
+ for (const animation of c.animations) animation.playbackRate = rate;
30
+ },
31
+ wake() {
32
+ if (!c.animations.length || c.rafId) return;
33
+ c.lastTs = 0;
34
+ c.rafId = requestAnimationFrame(tick);
35
+ },
36
+ setEngaged(next) {
37
+ c.engaged = next && c.pauseOnHover;
38
+ c.wake();
39
+ }
40
+ };
41
+ function tick(ts) {
42
+ const dt = c.lastTs ? Math.min(ts - c.lastTs, 64) : 16;
43
+ c.lastTs = ts;
44
+ const target = c.engaged ? 0 : 1;
45
+ c.factor += (target - c.factor) * (1 - Math.exp(-dt / EASE_TAU));
46
+ if (Math.abs(target - c.factor) < SETTLE_EPSILON) c.factor = target;
47
+ c.applyRate();
48
+ if (c.factor === target) {
49
+ c.rafId = 0;
50
+ c.lastTs = 0;
51
+ return;
52
+ }
53
+ c.rafId = requestAnimationFrame(tick);
54
+ }
55
+ return c;
56
+ }
5
57
  function Marquee({
6
58
  className,
7
59
  reverse = false,
8
60
  pauseOnHover = false,
9
61
  vertical = false,
10
62
  repeat = 4,
63
+ fade = true,
64
+ speed = 1,
11
65
  children
12
66
  }) {
67
+ const reduced = useReducedMotion();
68
+ const rootRef = useRef(null);
69
+ const [upgraded, setUpgraded] = useState(false);
70
+ const safeSpeed = Number.isFinite(speed) && speed > 0 ? speed : 1;
71
+ const conveyorRef = useRef(null);
72
+ if (!conveyorRef.current) conveyorRef.current = createConveyor();
73
+ const conveyor = conveyorRef.current;
74
+ useEffect(() => {
75
+ conveyor.speed = safeSpeed;
76
+ conveyor.applyRate();
77
+ }, [conveyor, safeSpeed]);
78
+ useEffect(() => {
79
+ conveyor.pauseOnHover = pauseOnHover;
80
+ if (!pauseOnHover && conveyor.engaged) conveyor.setEngaged(false);
81
+ }, [conveyor, pauseOnHover]);
82
+ useEffect(() => {
83
+ const el = rootRef.current;
84
+ if (!el || reduced) return;
85
+ if (typeof Element === "undefined" || typeof Element.prototype.animate !== "function") return;
86
+ const styles = getComputedStyle(el);
87
+ const duration = parseDuration(styles.getPropertyValue("--duration"));
88
+ const gapPx = Number.parseFloat(vertical ? styles.rowGap : styles.columnGap) || 0;
89
+ const axis = vertical ? "translateY" : "translateX";
90
+ const keyframes = [
91
+ { transform: `${axis}(0)` },
92
+ { transform: `${axis}(calc(-100% - ${gapPx}px))` }
93
+ ];
94
+ const tracks = Array.from(el.querySelectorAll(":scope > [data-marquee-track]"));
95
+ let startTime = 0;
96
+ if (conveyor.resumeAt !== null) {
97
+ startTime = (reverse ? 1 - conveyor.resumeAt : conveyor.resumeAt) * duration;
98
+ } else {
99
+ const running = tracks[0]?.getAnimations?.()[0];
100
+ if (running && typeof running.currentTime === "number") {
101
+ startTime = running.currentTime * conveyor.speed;
102
+ }
103
+ }
104
+ conveyor.animations = tracks.map(
105
+ (track) => track.animate(keyframes, {
106
+ duration,
107
+ iterations: Infinity,
108
+ easing: "linear",
109
+ direction: reverse ? "reverse" : "normal"
110
+ })
111
+ ).filter(Boolean);
112
+ for (const animation of conveyor.animations) animation.currentTime = startTime % duration;
113
+ conveyor.applyRate();
114
+ setUpgraded(true);
115
+ if (conveyor.factor !== (conveyor.engaged ? 0 : 1)) conveyor.wake();
116
+ return () => {
117
+ if (conveyor.rafId) cancelAnimationFrame(conveyor.rafId);
118
+ conveyor.rafId = 0;
119
+ const t = conveyor.animations[0]?.currentTime;
120
+ if (typeof t === "number") {
121
+ const progress = t % duration / duration;
122
+ conveyor.resumeAt = reverse ? 1 - progress : progress;
123
+ } else {
124
+ conveyor.resumeAt = null;
125
+ }
126
+ for (const animation of conveyor.animations) animation.cancel?.();
127
+ conveyor.animations = [];
128
+ setUpgraded(false);
129
+ };
130
+ }, [conveyor, vertical, reverse, repeat, className, reduced]);
131
+ function onFocusOut(event) {
132
+ if (!rootRef.current?.contains(event.relatedTarget)) conveyor.setEngaged(false);
133
+ }
13
134
  return /* @__PURE__ */ jsx(
14
135
  "div",
15
136
  {
137
+ ref: rootRef,
16
138
  className: cn(
17
- "fancy-marquee group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
18
- vertical ? "flex-col" : "flex-row",
139
+ "marquee group flex [gap:var(--gap)] overflow-hidden p-2 [--duration:40s] [--gap:1rem]",
140
+ vertical ? "marquee-vertical flex-col" : "flex-row",
141
+ fade && "marquee-fade",
142
+ upgraded && "marquee-upgraded",
19
143
  className
20
144
  ),
145
+ style: { "--marquee-speed": safeSpeed },
146
+ "data-paused-on-hover": pauseOnHover ? "" : void 0,
147
+ onPointerEnter: () => conveyor.setEngaged(true),
148
+ onPointerLeave: () => conveyor.setEngaged(false),
149
+ onFocus: () => conveyor.setEngaged(true),
150
+ onBlur: onFocusOut,
21
151
  children: Array.from({ length: repeat }, (_, index) => /* @__PURE__ */ jsx(
22
152
  "div",
23
153
  {
154
+ "data-marquee-track": "",
155
+ "aria-hidden": index > 0 ? "true" : void 0,
24
156
  className: cn(
25
157
  "flex shrink-0 justify-around [gap:var(--gap)]",
26
158
  vertical ? "animate-marquee-vertical flex-col" : "animate-marquee flex-row",
@@ -4,5 +4,6 @@ export interface ReviewCardProps {
4
4
  username: string;
5
5
  body: string;
6
6
  }
7
- /** A ready-made card for testimonial-style marquees. */
7
+ /** A ready-made card for testimonial-style marquees: a nested double frame,
8
+ * a hairline shell holding a hairline inner panel. */
8
9
  export declare function ReviewCard({ img, name, username, body }: ReviewCardProps): import("react").JSX.Element;