fancy-ui-react 0.2.1 → 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.
- package/README.md +56 -20
- package/dist/components/animated-beam/AnimatedBeam.d.ts +47 -11
- package/dist/components/animated-beam/AnimatedBeam.js +293 -118
- package/dist/components/animated-tooltip/AnimatedTooltip.d.ts +10 -1
- package/dist/components/animated-tooltip/AnimatedTooltip.js +170 -101
- package/dist/components/bento-grid/BentoFrame.d.ts +16 -0
- package/dist/components/bento-grid/BentoFrame.js +28 -0
- package/dist/components/bento-grid/BentoGrid.d.ts +5 -1
- package/dist/components/bento-grid/BentoGrid.js +71 -2
- package/dist/components/bento-grid/BentoGridCard.js +34 -26
- package/dist/components/bento-grid/BentoGridItem.js +10 -10
- package/dist/components/border-beam/BorderBeam.d.ts +55 -10
- package/dist/components/border-beam/BorderBeam.js +93 -16
- package/dist/components/button/Button.js +5 -1
- package/dist/components/compare/Compare.d.ts +32 -8
- package/dist/components/compare/Compare.js +213 -95
- package/dist/components/container-scroll/ContainerScroll.d.ts +26 -4
- package/dist/components/container-scroll/ContainerScroll.js +88 -49
- package/dist/components/datamosh-transition/DatamoshTransition.d.ts +76 -0
- package/dist/components/datamosh-transition/DatamoshTransition.js +368 -0
- package/dist/components/datamosh-transition/datamosh-transition-core.d.ts +180 -0
- package/dist/components/datamosh-transition/datamosh-transition-core.js +370 -0
- package/dist/components/datamosh-transition/index.d.ts +3 -0
- package/dist/components/dock/Dock.d.ts +10 -7
- package/dist/components/dock/Dock.js +76 -31
- package/dist/components/dock/DockIcon.d.ts +14 -3
- package/dist/components/dock/DockIcon.js +23 -19
- package/dist/components/dock/DockSeparator.d.ts +4 -1
- package/dist/components/dock/DockSeparator.js +5 -2
- package/dist/components/dock/types.d.ts +13 -6
- package/dist/components/dropdown-menu/menu-shared.d.ts +11 -8
- package/dist/components/editorial-engine/engine.js +1 -14
- package/dist/components/fireworks-hdr/webgl2-renderer.js +0 -19
- package/dist/components/fireworks-hdr/webgpu-renderer.js +3 -22
- package/dist/components/flip-card/FlipCard.d.ts +42 -1
- package/dist/components/flip-card/FlipCard.js +178 -25
- package/dist/components/flip-card/index.d.ts +1 -1
- package/dist/components/fluid-cursor/FluidCursor.js +5 -4
- package/dist/components/fluid-cursor/dev.d.ts +1 -0
- package/dist/components/fluid-cursor/dev.js +8 -0
- package/dist/components/fluid-cursor/fluid-shared.d.ts +0 -2
- package/dist/components/fluid-cursor/fluid-shared.js +0 -2
- package/dist/components/fluid-cursor/webgpu-engine.js +10 -24
- package/dist/components/glow-border/GlowBorder.d.ts +41 -1
- package/dist/components/glow-border/GlowBorder.js +96 -16
- package/dist/components/glow-border/index.d.ts +2 -2
- package/dist/components/gradient-button/GradientButton.d.ts +10 -5
- package/dist/components/gradient-button/GradientButton.js +25 -18
- package/dist/components/image-trail-cursor/ImageTrailCursor.js +1 -0
- package/dist/components/image-trail-cursor/trail-variants.d.ts +37 -1
- package/dist/components/image-trail-cursor/trail-variants.js +331 -8
- package/dist/components/interactive-hover-button/InteractiveHoverButton.js +30 -35
- package/dist/components/marquee/Marquee.d.ts +7 -3
- package/dist/components/marquee/Marquee.js +134 -2
- package/dist/components/marquee/ReviewCard.d.ts +2 -1
- package/dist/components/marquee/ReviewCard.js +20 -9
- package/dist/components/meteors/Meteors.d.ts +32 -10
- package/dist/components/meteors/Meteors.js +53 -20
- package/dist/components/mosaic-glow/mosaic-glow-core.d.ts +98 -10
- package/dist/components/neon-border/NeonBorder.d.ts +24 -5
- package/dist/components/neon-border/NeonBorder.js +37 -7
- package/dist/components/pagination/Pagination.js +220 -82
- package/dist/components/rainbow-button/RainbowButton.js +13 -5
- package/dist/components/ripple-button/RippleButton.d.ts +20 -1
- package/dist/components/ripple-button/RippleButton.js +44 -21
- package/dist/components/shimmer-button/ShimmerButton.d.ts +5 -5
- package/dist/components/shimmer-button/ShimmerButton.js +17 -19
- package/dist/components/smooth-cursor/SmoothCursor.d.ts +13 -9
- package/dist/components/smooth-cursor/SmoothCursor.js +114 -163
- package/dist/components/smooth-cursor/smooth-cursor-core.d.ts +85 -0
- package/dist/components/smooth-cursor/smooth-cursor-core.js +327 -0
- package/dist/components/stepper/Step.js +32 -19
- package/dist/components/stepper/Stepper.js +18 -2
- package/dist/components/stepper/types.d.ts +13 -0
- package/dist/components/timeline/Timeline.d.ts +8 -1
- package/dist/components/timeline/Timeline.js +165 -76
- package/dist/index.d.ts +1 -0
- package/dist/index.js +6 -1
- package/dist/internals/markdown.d.ts +5 -0
- package/dist/internals/motion/haptics.d.ts +0 -4
- package/dist/internals/motion/presence-core.d.ts +59 -0
- package/dist/internals/motion/presence-core.js +162 -0
- package/dist/internals/motion/presence.d.ts +2 -4
- package/dist/internals/motion/presence.js +1 -156
- package/dist/internals/motion/raf.d.ts +1 -1
- package/dist/internals/motion/stagger.d.ts +3 -4
- package/dist/internals/motion/tokens.js +4 -0
- package/dist/internals/motion/transitions.d.ts +17 -17
- package/dist/internals/motion/types.d.ts +6 -7
- package/dist/internals/relative-time.d.ts +1 -1
- package/dist/styles.css +3545 -204
- package/package.json +18 -2
- package/dist/components/fireworks-hdr/webgpu-types.d.ts +0 -159
|
@@ -1,10 +1,32 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* ContainerScroll - a scroll-driven aperture
|
|
4
|
+
*
|
|
5
|
+
* The card starts shut: a slim horizontal slit with a bright seam of light
|
|
6
|
+
* across its middle. As the section scrolls into view the slit opens
|
|
7
|
+
* vertically to the full card, the seam splits into two lips that ride the
|
|
8
|
+
* opening edges and fade, the content settles from a slight zoom into
|
|
9
|
+
* focus, and the title above blurs away to hand the stage to the card.
|
|
10
|
+
* No 3D tilt, no perspective.
|
|
11
|
+
*/
|
|
2
12
|
export interface ContainerScrollProps {
|
|
3
|
-
/** Additional CSS classes */
|
|
13
|
+
/** Additional CSS classes on the section */
|
|
4
14
|
className?: string;
|
|
5
|
-
/**
|
|
15
|
+
/** Title shown above the card (blurs and fades as the card opens) */
|
|
6
16
|
titleContent?: ReactNode;
|
|
7
|
-
/** Content
|
|
17
|
+
/** Content revealed inside the card */
|
|
8
18
|
cardContent?: ReactNode;
|
|
19
|
+
/** Seam colour (any CSS colour). Defaults to a soft blue. */
|
|
20
|
+
accent?: string;
|
|
21
|
+
/** Second seam tint, blended towards the seam ends. Defaults to a soft lilac. */
|
|
22
|
+
accentSecondary?: string;
|
|
9
23
|
}
|
|
10
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Raw scroll progress of the card track, measured at its centre (where the
|
|
26
|
+
* slit is): 0 while the slit is still in the bottom 8% of the viewport,
|
|
27
|
+
* 1 once it has risen to just above the middle of the viewport.
|
|
28
|
+
*/
|
|
29
|
+
export declare function apertureProgress(top: number, height: number, viewport: number): number;
|
|
30
|
+
/** Smoothstep: a gentle start and a slow settle, so the opening reads as a lens, not a shutter. */
|
|
31
|
+
export declare function apertureEase(p: number): number;
|
|
32
|
+
export declare function ContainerScroll({ className, titleContent, cardContent, accent, accentSecondary, }: ContainerScrollProps): import("react").JSX.Element;
|
|
@@ -2,73 +2,112 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useRef, useState } from "react";
|
|
4
4
|
import { useIsomorphicLayoutEffect } from "../../internals/dom/ssr.js";
|
|
5
|
+
import { useReducedMotion } from "../../internals/motion/media-query.js";
|
|
6
|
+
import { rafThrottle } from "../../internals/motion/raf.js";
|
|
5
7
|
import { cn } from "../../utils.js";
|
|
8
|
+
/* empty css */
|
|
9
|
+
function apertureProgress(top, height, viewport) {
|
|
10
|
+
if (viewport <= 0) return 1;
|
|
11
|
+
const centre = top + height / 2;
|
|
12
|
+
const p = (viewport * 0.92 - centre) / (viewport * 0.4);
|
|
13
|
+
return p < 0 ? 0 : p > 1 ? 1 : p;
|
|
14
|
+
}
|
|
15
|
+
function apertureEase(p) {
|
|
16
|
+
const t = p < 0 ? 0 : p > 1 ? 1 : p;
|
|
17
|
+
return t * t * (3 - 2 * t);
|
|
18
|
+
}
|
|
6
19
|
function ContainerScroll({
|
|
7
20
|
className = "",
|
|
8
21
|
titleContent,
|
|
9
|
-
cardContent
|
|
22
|
+
cardContent,
|
|
23
|
+
accent,
|
|
24
|
+
accentSecondary
|
|
10
25
|
}) {
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const [
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
26
|
+
const reduced = useReducedMotion();
|
|
27
|
+
const trackRef = useRef(null);
|
|
28
|
+
const [raw, setRaw] = useState(0);
|
|
29
|
+
const progress = reduced ? 1 : raw;
|
|
30
|
+
const open = apertureEase(progress);
|
|
31
|
+
const styleVars = {
|
|
32
|
+
"--cs-progress": progress.toFixed(4),
|
|
33
|
+
"--cs-open": open.toFixed(4)
|
|
34
|
+
};
|
|
35
|
+
if (accent) styleVars["--cs-accent"] = accent;
|
|
36
|
+
if (accentSecondary) styleVars["--cs-accent-2"] = accentSecondary;
|
|
18
37
|
useIsomorphicLayoutEffect(() => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const windowHeight = window.innerHeight;
|
|
26
|
-
const progress = 1 - Math.max(0, rect.bottom - window.scrollY) / windowHeight;
|
|
27
|
-
setScrollYProgress(Math.max(0, Math.min(1, progress)));
|
|
38
|
+
const track = trackRef.current;
|
|
39
|
+
if (!track || reduced) return;
|
|
40
|
+
function measure() {
|
|
41
|
+
if (!track) return;
|
|
42
|
+
const rect = track.getBoundingClientRect();
|
|
43
|
+
setRaw(apertureProgress(rect.top, rect.height, window.innerHeight));
|
|
28
44
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
window.addEventListener("scroll",
|
|
33
|
-
window.addEventListener("resize",
|
|
45
|
+
const update = rafThrottle(measure);
|
|
46
|
+
measure();
|
|
47
|
+
const opts = { capture: true, passive: true };
|
|
48
|
+
window.addEventListener("scroll", update, opts);
|
|
49
|
+
window.addEventListener("resize", update, { passive: true });
|
|
34
50
|
return () => {
|
|
35
|
-
|
|
36
|
-
window.removeEventListener("scroll",
|
|
37
|
-
window.removeEventListener("resize",
|
|
51
|
+
update.cancel();
|
|
52
|
+
window.removeEventListener("scroll", update, opts);
|
|
53
|
+
window.removeEventListener("resize", update);
|
|
38
54
|
};
|
|
39
|
-
}, []);
|
|
55
|
+
}, [reduced]);
|
|
40
56
|
return /* @__PURE__ */ jsx(
|
|
41
57
|
"div",
|
|
42
58
|
{
|
|
43
|
-
ref: containerRef,
|
|
44
59
|
className: cn(
|
|
45
|
-
"relative flex h-[
|
|
60
|
+
"cs-root relative flex h-[48rem] w-full items-start justify-center p-2 md:h-[64rem] md:p-10",
|
|
46
61
|
className
|
|
47
62
|
),
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
style: styleVars,
|
|
64
|
+
"data-reduced-motion": reduced ? "" : void 0,
|
|
65
|
+
children: /* @__PURE__ */ jsxs("div", { className: "relative w-full py-10 md:py-16", children: [
|
|
66
|
+
/* @__PURE__ */ jsx("div", { className: "cs-title mx-auto max-w-5xl text-center", children: titleContent }),
|
|
67
|
+
/* @__PURE__ */ jsx("div", { ref: trackRef, className: "cs-track relative mx-auto mt-10 w-full max-w-5xl md:mt-12", children: /* @__PURE__ */ jsxs("div", { className: "cs-stage relative aspect-[4/3] w-full sm:aspect-[16/10]", children: [
|
|
68
|
+
/* @__PURE__ */ jsx(
|
|
69
|
+
"div",
|
|
70
|
+
{
|
|
71
|
+
className: "cs-shadow pointer-events-none absolute inset-0",
|
|
72
|
+
"aria-hidden": "true"
|
|
73
|
+
}
|
|
74
|
+
),
|
|
75
|
+
/* @__PURE__ */ jsx("div", { className: "cs-card absolute inset-0 p-1.5 md:p-2", children: /* @__PURE__ */ jsxs("div", { className: "cs-surface relative size-full overflow-hidden", children: [
|
|
76
|
+
/* @__PURE__ */ jsx("div", { className: "cs-content size-full", children: cardContent }),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"div",
|
|
79
|
+
{
|
|
80
|
+
className: "cs-veil pointer-events-none absolute inset-0",
|
|
81
|
+
"aria-hidden": "true"
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
] }) }),
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
className: "cs-lip cs-lip-top pointer-events-none absolute",
|
|
89
|
+
"aria-hidden": "true"
|
|
90
|
+
}
|
|
91
|
+
),
|
|
92
|
+
/* @__PURE__ */ jsx(
|
|
93
|
+
"div",
|
|
94
|
+
{
|
|
95
|
+
className: "cs-lip cs-lip-bottom pointer-events-none absolute",
|
|
96
|
+
"aria-hidden": "true"
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
/* @__PURE__ */ jsxs("div", { className: "cs-seam pointer-events-none absolute", "aria-hidden": "true", children: [
|
|
100
|
+
/* @__PURE__ */ jsx("span", { className: "cs-seam-haze" }),
|
|
101
|
+
/* @__PURE__ */ jsx("span", { className: "cs-seam-glow" }),
|
|
102
|
+
/* @__PURE__ */ jsx("span", { className: "cs-seam-line" })
|
|
103
|
+
] })
|
|
104
|
+
] }) })
|
|
68
105
|
] })
|
|
69
106
|
}
|
|
70
107
|
);
|
|
71
108
|
}
|
|
72
109
|
export {
|
|
73
|
-
ContainerScroll
|
|
110
|
+
ContainerScroll,
|
|
111
|
+
apertureEase,
|
|
112
|
+
apertureProgress
|
|
74
113
|
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "react";
|
|
2
|
+
import { type DatamoshEffect, type DatamoshPaletteName, type DatamoshSweep } from "./datamosh-transition-core.js";
|
|
3
|
+
export type DatamoshTransitionPhase = "idle" | "covering" | "covered" | "revealing";
|
|
4
|
+
/**
|
|
5
|
+
* What the forwarded ref carries. The Svelte source exposes `cover()`,
|
|
6
|
+
* `reveal()` and `play()` on the component instance and binds the overlay
|
|
7
|
+
* element through `ref`; React has no instance, so both travel through one
|
|
8
|
+
* handle: the three methods plus `element`.
|
|
9
|
+
*/
|
|
10
|
+
export interface DatamoshTransitionHandle {
|
|
11
|
+
/** Cover the page. Resolves once every column is down. */
|
|
12
|
+
cover: () => Promise<void>;
|
|
13
|
+
/** Clear the page. Resolves once every column has fallen away. */
|
|
14
|
+
reveal: () => Promise<void>;
|
|
15
|
+
/** Cover, then reveal — a one-shot flash. */
|
|
16
|
+
play: () => Promise<void>;
|
|
17
|
+
/** The overlay element. */
|
|
18
|
+
readonly element: HTMLDivElement | null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* DatamoshTransition — a page-transition overlay that looks like a corrupted
|
|
22
|
+
* video decode.
|
|
23
|
+
*
|
|
24
|
+
* A fixed grid of columns (narrow on the left, wide on the right) fills with
|
|
25
|
+
* flat blocks of saturated colour. Inside each column a stack of tiles falls
|
|
26
|
+
* along one sigmoid: slivers at the edges, one tile snapping open through
|
|
27
|
+
* the middle. `cover()` drops the columns over the page like a curtain,
|
|
28
|
+
* right to left; `reveal()` lets them fall away. Nothing moves sideways.
|
|
29
|
+
*
|
|
30
|
+
* Mount once (root layout) and drive it from your router's navigation hook.
|
|
31
|
+
*/
|
|
32
|
+
export interface DatamoshTransitionProps extends Omit<HTMLAttributes<HTMLDivElement>, "color"> {
|
|
33
|
+
/** Additional classes on the overlay */
|
|
34
|
+
className?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Palette name or colour list (hex or rgb()). The lightest and darkest
|
|
37
|
+
* colours recur most often.
|
|
38
|
+
*/
|
|
39
|
+
colors?: string[] | DatamoshPaletteName;
|
|
40
|
+
/** Shape of the covering band: curtain, rise, split or interlace */
|
|
41
|
+
variant?: DatamoshEffect;
|
|
42
|
+
/** Order in which the columns move */
|
|
43
|
+
sweep?: DatamoshSweep;
|
|
44
|
+
/**
|
|
45
|
+
* Picture to decode: tiles take their colours from it instead of the
|
|
46
|
+
* palette. A URL (same-origin or CORS-enabled), an image or a canvas.
|
|
47
|
+
*/
|
|
48
|
+
source?: string | HTMLImageElement | HTMLCanvasElement;
|
|
49
|
+
/** Seed for the colour order — same seed, same strip */
|
|
50
|
+
seed?: number;
|
|
51
|
+
/** Number of columns */
|
|
52
|
+
columns?: number;
|
|
53
|
+
/** Column edge exponent: 1 = uniform grid, higher = wider columns to the right */
|
|
54
|
+
power?: number;
|
|
55
|
+
/** Tiles per column stack (forced odd) */
|
|
56
|
+
tiles?: number;
|
|
57
|
+
/** Time for one column to cover, ms */
|
|
58
|
+
coverDuration?: number;
|
|
59
|
+
/** Time for one column to clear, ms */
|
|
60
|
+
revealDuration?: number;
|
|
61
|
+
/** Delay between neighbouring columns, ms (rightmost first) */
|
|
62
|
+
stagger?: number;
|
|
63
|
+
/** Speed of the falling tiles, 1 = default */
|
|
64
|
+
speed?: number;
|
|
65
|
+
/** Fill the positioned parent instead of the viewport */
|
|
66
|
+
contained?: boolean;
|
|
67
|
+
/** Stacking order of the overlay */
|
|
68
|
+
zIndex?: number;
|
|
69
|
+
/** Called with every phase change (the Svelte `bind:phase`) */
|
|
70
|
+
onPhaseChange?: (phase: DatamoshTransitionPhase) => void;
|
|
71
|
+
/** Called once the page is fully covered */
|
|
72
|
+
oncovered?: () => void;
|
|
73
|
+
/** Called once the page is fully revealed */
|
|
74
|
+
onrevealed?: () => void;
|
|
75
|
+
}
|
|
76
|
+
export declare const DatamoshTransition: import("react").ForwardRefExoticComponent<DatamoshTransitionProps & import("react").RefAttributes<DatamoshTransitionHandle>>;
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef, useMemo, useState, useRef, useCallback, useImperativeHandle, useEffect } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
import { useLiveRef } from "../../internals/dom/use-live-ref.js";
|
|
6
|
+
import { useReducedMotion } from "../../internals/motion/media-query.js";
|
|
7
|
+
import { resolvePalette, buildStrip, columnDelays, paletteRoles, createCoverage, DEFAULT_POWER, oddTiles, DEFAULT_COLUMNS, DEFAULT_TILES, coverFit, sampleSource, columnEdges, settleCoverage, CYCLE, FALL_STAGGER, columnBand, easeEdge, columnFlow, columnTiles, sourceSlot, stripIndex, stepCoverage, BLEED } from "./datamosh-transition-core.js";
|
|
8
|
+
function currentDpr() {
|
|
9
|
+
return Math.min(typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1, 2);
|
|
10
|
+
}
|
|
11
|
+
const DatamoshTransition = forwardRef(
|
|
12
|
+
function DatamoshTransition2({
|
|
13
|
+
className,
|
|
14
|
+
colors = "broadcast",
|
|
15
|
+
variant = "curtain",
|
|
16
|
+
sweep = "right",
|
|
17
|
+
source,
|
|
18
|
+
seed = 1,
|
|
19
|
+
columns = DEFAULT_COLUMNS,
|
|
20
|
+
power = DEFAULT_POWER,
|
|
21
|
+
tiles = DEFAULT_TILES,
|
|
22
|
+
coverDuration = 380,
|
|
23
|
+
revealDuration = 480,
|
|
24
|
+
stagger = 28,
|
|
25
|
+
speed = 1,
|
|
26
|
+
contained = false,
|
|
27
|
+
zIndex = 9999,
|
|
28
|
+
onPhaseChange,
|
|
29
|
+
oncovered,
|
|
30
|
+
onrevealed,
|
|
31
|
+
style,
|
|
32
|
+
...restProps
|
|
33
|
+
}, ref) {
|
|
34
|
+
const reduced = useReducedMotion();
|
|
35
|
+
const palette = useMemo(() => resolvePalette(colors), [colors]);
|
|
36
|
+
const columnsC = Math.max(1, Math.round(Number.isFinite(columns) ? columns : DEFAULT_COLUMNS));
|
|
37
|
+
const powerC = Number.isFinite(power) && power > 0 ? power : DEFAULT_POWER;
|
|
38
|
+
const tilesC = oddTiles(tiles);
|
|
39
|
+
const speedC = Number.isFinite(speed) && speed > 0 ? speed : 1;
|
|
40
|
+
const strip = useMemo(() => buildStrip(seed, palette), [seed, palette]);
|
|
41
|
+
const delays = useMemo(
|
|
42
|
+
() => columnDelays(
|
|
43
|
+
columnsC,
|
|
44
|
+
sweep,
|
|
45
|
+
Math.max(0, Number.isFinite(stagger) ? stagger : 0) / 1e3,
|
|
46
|
+
seed
|
|
47
|
+
),
|
|
48
|
+
[columnsC, sweep, stagger, seed]
|
|
49
|
+
);
|
|
50
|
+
const backing = palette[paletteRoles(palette).darks[0] ?? 0] ?? "#14101f";
|
|
51
|
+
const [phase, setPhaseState] = useState("idle");
|
|
52
|
+
const hostRef = useRef(null);
|
|
53
|
+
const canvasRef = useRef(null);
|
|
54
|
+
const live = useLiveRef({
|
|
55
|
+
reduced,
|
|
56
|
+
palette,
|
|
57
|
+
columnsC,
|
|
58
|
+
powerC,
|
|
59
|
+
tilesC,
|
|
60
|
+
speedC,
|
|
61
|
+
strip,
|
|
62
|
+
delays,
|
|
63
|
+
backing,
|
|
64
|
+
variant,
|
|
65
|
+
coverDuration,
|
|
66
|
+
revealDuration,
|
|
67
|
+
onPhaseChange,
|
|
68
|
+
oncovered,
|
|
69
|
+
onrevealed
|
|
70
|
+
});
|
|
71
|
+
const s = useRef({
|
|
72
|
+
ctx: null,
|
|
73
|
+
w: 0,
|
|
74
|
+
h: 0,
|
|
75
|
+
edges: [],
|
|
76
|
+
cov: createCoverage(DEFAULT_COLUMNS),
|
|
77
|
+
direction: "reveal",
|
|
78
|
+
phase: "idle",
|
|
79
|
+
phaseTime: 0,
|
|
80
|
+
elapsed: 0,
|
|
81
|
+
rafId: null,
|
|
82
|
+
lastT: 0,
|
|
83
|
+
pending: [],
|
|
84
|
+
sourceImage: null,
|
|
85
|
+
sourceSize: { w: 0, h: 0 },
|
|
86
|
+
sourceTable: null
|
|
87
|
+
}).current;
|
|
88
|
+
const engine = useMemo(() => {
|
|
89
|
+
function setPhase(next) {
|
|
90
|
+
if (s.phase === next) return;
|
|
91
|
+
s.phase = next;
|
|
92
|
+
setPhaseState(next);
|
|
93
|
+
live.current.onPhaseChange?.(next);
|
|
94
|
+
}
|
|
95
|
+
function settlePending() {
|
|
96
|
+
const list = s.pending;
|
|
97
|
+
s.pending = [];
|
|
98
|
+
for (const resolve of list) resolve();
|
|
99
|
+
}
|
|
100
|
+
function clear() {
|
|
101
|
+
s.ctx?.clearRect(0, 0, s.w, s.h);
|
|
102
|
+
}
|
|
103
|
+
function draw() {
|
|
104
|
+
const c = s.ctx;
|
|
105
|
+
const p = live.current;
|
|
106
|
+
if (!c || !s.edges.length) return;
|
|
107
|
+
const { w, h } = s;
|
|
108
|
+
c.clearRect(0, 0, w, h);
|
|
109
|
+
const bleed = Math.round(BLEED * h);
|
|
110
|
+
const len = p.palette.length;
|
|
111
|
+
const t = p.reduced ? CYCLE * 0.45 + p.columnsC * FALL_STAGGER : s.elapsed;
|
|
112
|
+
for (let i = 0; i < p.columnsC; i++) {
|
|
113
|
+
const x0 = s.edges[i];
|
|
114
|
+
const cw = s.edges[i + 1] - x0;
|
|
115
|
+
if (cw <= 0) continue;
|
|
116
|
+
const band = columnBand(
|
|
117
|
+
p.variant,
|
|
118
|
+
i,
|
|
119
|
+
easeEdge(s.cov.top[i] ?? 0),
|
|
120
|
+
easeEdge(s.cov.bot[i] ?? 0)
|
|
121
|
+
);
|
|
122
|
+
const bandTop = Math.round(band.y0 * h);
|
|
123
|
+
const bandBot = Math.round(band.y1 * h);
|
|
124
|
+
if (bandBot <= bandTop) continue;
|
|
125
|
+
c.fillStyle = p.backing;
|
|
126
|
+
c.fillRect(x0, bandTop, cw, bandBot - bandTop);
|
|
127
|
+
const flow = columnFlow(t, i, p.columnsC, p.speedC);
|
|
128
|
+
for (const tile of columnTiles(flow, p.tilesC, h, bleed)) {
|
|
129
|
+
const top = band.mirror ? h - tile.bot : tile.top;
|
|
130
|
+
const bot = band.mirror ? h - tile.top : tile.bot;
|
|
131
|
+
const y0 = Math.max(bandTop, top);
|
|
132
|
+
const y1 = Math.min(bandBot, bot);
|
|
133
|
+
if (y1 <= y0) continue;
|
|
134
|
+
c.fillStyle = s.sourceTable?.[i]?.[sourceSlot(tile.id, i, p.tilesC)] ?? p.palette[stripIndex(p.strip, tile.id, i) % len];
|
|
135
|
+
c.fillRect(x0, y0, cw, y1 - y0);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function stopLoop() {
|
|
140
|
+
if (s.rafId !== null && typeof cancelAnimationFrame === "function")
|
|
141
|
+
cancelAnimationFrame(s.rafId);
|
|
142
|
+
s.rafId = null;
|
|
143
|
+
}
|
|
144
|
+
function requestFrame() {
|
|
145
|
+
if (s.rafId !== null || !s.ctx || typeof requestAnimationFrame !== "function") return;
|
|
146
|
+
s.lastT = 0;
|
|
147
|
+
s.rafId = requestAnimationFrame(tick);
|
|
148
|
+
}
|
|
149
|
+
function finish(dir) {
|
|
150
|
+
settleCoverage(s.cov, dir);
|
|
151
|
+
setPhase(dir === "cover" ? "covered" : "idle");
|
|
152
|
+
if (dir === "reveal") {
|
|
153
|
+
stopLoop();
|
|
154
|
+
clear();
|
|
155
|
+
} else if (!live.current.reduced) {
|
|
156
|
+
requestFrame();
|
|
157
|
+
} else {
|
|
158
|
+
draw();
|
|
159
|
+
}
|
|
160
|
+
settlePending();
|
|
161
|
+
if (dir === "cover") live.current.oncovered?.();
|
|
162
|
+
else live.current.onrevealed?.();
|
|
163
|
+
}
|
|
164
|
+
function tick(now) {
|
|
165
|
+
s.rafId = null;
|
|
166
|
+
if (!s.ctx) return;
|
|
167
|
+
const dt = s.lastT ? Math.min(0.05, Math.max(0, (now - s.lastT) / 1e3)) : 0;
|
|
168
|
+
s.lastT = now;
|
|
169
|
+
s.elapsed += dt;
|
|
170
|
+
if (s.phase === "covering" || s.phase === "revealing") {
|
|
171
|
+
s.phaseTime += dt;
|
|
172
|
+
const p = live.current;
|
|
173
|
+
const duration = (s.direction === "cover" ? p.coverDuration : p.revealDuration) / 1e3;
|
|
174
|
+
const done = stepCoverage(s.cov, s.direction, s.phaseTime, dt, duration, p.delays);
|
|
175
|
+
if (done) {
|
|
176
|
+
finish(s.direction);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
draw();
|
|
181
|
+
if (s.phase !== "idle") s.rafId = requestAnimationFrame(tick);
|
|
182
|
+
}
|
|
183
|
+
function run(dir) {
|
|
184
|
+
settlePending();
|
|
185
|
+
const promise = new Promise((resolve) => s.pending.push(resolve));
|
|
186
|
+
if (dir === "reveal" && s.phase === "idle" || dir === "cover" && s.phase === "covered") {
|
|
187
|
+
settlePending();
|
|
188
|
+
return promise;
|
|
189
|
+
}
|
|
190
|
+
s.direction = dir;
|
|
191
|
+
s.phaseTime = 0;
|
|
192
|
+
setPhase(dir === "cover" ? "covering" : "revealing");
|
|
193
|
+
const instant = !s.ctx || live.current.reduced || typeof requestAnimationFrame !== "function" || typeof document !== "undefined" && document.hidden;
|
|
194
|
+
if (instant) finish(dir);
|
|
195
|
+
else requestFrame();
|
|
196
|
+
return promise;
|
|
197
|
+
}
|
|
198
|
+
function buildSourceTable() {
|
|
199
|
+
s.sourceTable = null;
|
|
200
|
+
if (!s.sourceImage || !s.edges.length || typeof document === "undefined") return;
|
|
201
|
+
const pw = 96;
|
|
202
|
+
const ph = Math.max(8, Math.round(pw * s.h / Math.max(1, s.w)));
|
|
203
|
+
try {
|
|
204
|
+
const scratch = document.createElement("canvas");
|
|
205
|
+
scratch.width = pw;
|
|
206
|
+
scratch.height = ph;
|
|
207
|
+
const sctx = scratch.getContext("2d", { willReadFrequently: true });
|
|
208
|
+
if (!sctx) return;
|
|
209
|
+
const fit = coverFit(s.sourceSize.w, s.sourceSize.h, pw, ph);
|
|
210
|
+
sctx.drawImage(s.sourceImage, fit.sx, fit.sy, fit.sw, fit.sh, 0, 0, pw, ph);
|
|
211
|
+
const { data } = sctx.getImageData(0, 0, pw, ph);
|
|
212
|
+
const norm = s.edges.map((e) => e / Math.max(1, s.w));
|
|
213
|
+
s.sourceTable = sampleSource(data, pw, ph, norm, live.current.tilesC);
|
|
214
|
+
} catch {
|
|
215
|
+
s.sourceTable = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function measure() {
|
|
219
|
+
const host = hostRef.current;
|
|
220
|
+
const canvas = canvasRef.current;
|
|
221
|
+
if (!host || !canvas || !s.ctx) return;
|
|
222
|
+
const p = live.current;
|
|
223
|
+
const dpr = currentDpr();
|
|
224
|
+
s.w = Math.max(1, Math.round(host.clientWidth * dpr));
|
|
225
|
+
s.h = Math.max(1, Math.round(host.clientHeight * dpr));
|
|
226
|
+
canvas.width = s.w;
|
|
227
|
+
canvas.height = s.h;
|
|
228
|
+
s.ctx.imageSmoothingEnabled = false;
|
|
229
|
+
s.edges = columnEdges(s.w, p.columnsC, p.powerC);
|
|
230
|
+
buildSourceTable();
|
|
231
|
+
if (s.cov.top.length !== p.columnsC) {
|
|
232
|
+
const covered = s.phase === "covered" || s.phase === "covering";
|
|
233
|
+
s.cov = createCoverage(p.columnsC, covered);
|
|
234
|
+
}
|
|
235
|
+
if (s.phase !== "idle") draw();
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
run,
|
|
239
|
+
draw,
|
|
240
|
+
finish,
|
|
241
|
+
measure,
|
|
242
|
+
stopLoop,
|
|
243
|
+
requestFrame,
|
|
244
|
+
settlePending,
|
|
245
|
+
buildSourceTable
|
|
246
|
+
};
|
|
247
|
+
}, [live, s]);
|
|
248
|
+
const cover = useCallback(() => engine.run("cover"), [engine]);
|
|
249
|
+
const reveal = useCallback(() => engine.run("reveal"), [engine]);
|
|
250
|
+
const play = useCallback(async () => {
|
|
251
|
+
await engine.run("cover");
|
|
252
|
+
await engine.run("reveal");
|
|
253
|
+
}, [engine]);
|
|
254
|
+
useImperativeHandle(
|
|
255
|
+
ref,
|
|
256
|
+
() => ({
|
|
257
|
+
cover,
|
|
258
|
+
reveal,
|
|
259
|
+
play,
|
|
260
|
+
get element() {
|
|
261
|
+
return hostRef.current;
|
|
262
|
+
}
|
|
263
|
+
}),
|
|
264
|
+
[cover, reveal, play]
|
|
265
|
+
);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
const host = hostRef.current;
|
|
268
|
+
const canvas = canvasRef.current;
|
|
269
|
+
if (!host || !canvas) return;
|
|
270
|
+
s.ctx = canvas.getContext("2d");
|
|
271
|
+
if (!s.ctx) return;
|
|
272
|
+
engine.measure();
|
|
273
|
+
let ro;
|
|
274
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
275
|
+
ro = new ResizeObserver(() => engine.measure());
|
|
276
|
+
ro.observe(host);
|
|
277
|
+
}
|
|
278
|
+
const onVisibility = () => {
|
|
279
|
+
if (!document.hidden) {
|
|
280
|
+
if (s.phase !== "idle") engine.requestFrame();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
engine.stopLoop();
|
|
284
|
+
if (s.phase === "covering") engine.finish("cover");
|
|
285
|
+
else if (s.phase === "revealing") engine.finish("reveal");
|
|
286
|
+
};
|
|
287
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
288
|
+
return () => {
|
|
289
|
+
engine.stopLoop();
|
|
290
|
+
ro?.disconnect();
|
|
291
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
292
|
+
engine.settlePending();
|
|
293
|
+
s.ctx = null;
|
|
294
|
+
};
|
|
295
|
+
}, [engine, s]);
|
|
296
|
+
const gridInit = useRef(false);
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
if (!gridInit.current) {
|
|
299
|
+
gridInit.current = true;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
engine.measure();
|
|
303
|
+
}, [columnsC, powerC, engine]);
|
|
304
|
+
useEffect(() => {
|
|
305
|
+
if (!reduced) return;
|
|
306
|
+
if (s.phase === "covering") engine.finish("cover");
|
|
307
|
+
else if (s.phase === "revealing") engine.finish("reveal");
|
|
308
|
+
else if (s.phase === "covered") {
|
|
309
|
+
engine.stopLoop();
|
|
310
|
+
engine.draw();
|
|
311
|
+
}
|
|
312
|
+
}, [reduced, engine, s]);
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
s.sourceImage = null;
|
|
315
|
+
s.sourceTable = null;
|
|
316
|
+
const src = source;
|
|
317
|
+
if (!src || typeof window === "undefined") return;
|
|
318
|
+
let cancelled = false;
|
|
319
|
+
const ready = (img2, iw, ih) => {
|
|
320
|
+
if (cancelled || iw <= 0 || ih <= 0) return;
|
|
321
|
+
s.sourceImage = img2;
|
|
322
|
+
s.sourceSize = { w: iw, h: ih };
|
|
323
|
+
engine.buildSourceTable();
|
|
324
|
+
if (s.phase !== "idle") engine.draw();
|
|
325
|
+
};
|
|
326
|
+
if (typeof src !== "string") {
|
|
327
|
+
if (src instanceof HTMLCanvasElement) ready(src, src.width, src.height);
|
|
328
|
+
else if (src.complete && src.naturalWidth) ready(src, src.naturalWidth, src.naturalHeight);
|
|
329
|
+
else
|
|
330
|
+
src.addEventListener("load", () => ready(src, src.naturalWidth, src.naturalHeight), {
|
|
331
|
+
once: true
|
|
332
|
+
});
|
|
333
|
+
return () => {
|
|
334
|
+
cancelled = true;
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
const img = new Image();
|
|
338
|
+
img.decoding = "async";
|
|
339
|
+
if (!src.startsWith("data:")) img.crossOrigin = "anonymous";
|
|
340
|
+
img.onload = () => ready(img, img.naturalWidth || 300, img.naturalHeight || 150);
|
|
341
|
+
img.src = src;
|
|
342
|
+
return () => {
|
|
343
|
+
cancelled = true;
|
|
344
|
+
img.onload = null;
|
|
345
|
+
};
|
|
346
|
+
}, [source, tilesC, engine, s]);
|
|
347
|
+
return /* @__PURE__ */ jsx(
|
|
348
|
+
"div",
|
|
349
|
+
{
|
|
350
|
+
ref: hostRef,
|
|
351
|
+
className: cn(
|
|
352
|
+
"datamosh-transition inset-0",
|
|
353
|
+
contained ? "absolute" : "fixed",
|
|
354
|
+
phase === "idle" ? "pointer-events-none invisible" : "pointer-events-auto",
|
|
355
|
+
className
|
|
356
|
+
),
|
|
357
|
+
style: { zIndex, ...style },
|
|
358
|
+
"data-state": phase,
|
|
359
|
+
"aria-hidden": "true",
|
|
360
|
+
...restProps,
|
|
361
|
+
children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "block h-full w-full" })
|
|
362
|
+
}
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
);
|
|
366
|
+
export {
|
|
367
|
+
DatamoshTransition
|
|
368
|
+
};
|