apex-ui-kit 2.2.0 → 2.3.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/dist/ApexUI-Kit/templates/AiChat/AiChat.jsx +171 -0
- package/dist/ApexUI-Kit/templates/AiModelPrompt/AiModelPrompt.jsx +221 -0
- package/dist/ApexUI-Kit/templates/AiModelPrompt/index.js +2 -0
- package/dist/ApexUI-Kit/templates/AiModelPrompt/models.js +15 -0
- package/dist/ApexUI-Kit/templates/AiPromptBox/AiPromptBox.jsx +173 -0
- package/dist/ApexUI-Kit/templates/AiResponse/AiResponse.jsx +172 -0
- package/dist/ApexUI-Kit/templates/AiThinking/AiThinking.jsx +106 -0
- package/dist/ApexUI-Kit/templates/AiVoiceBars/AiVoiceBars.jsx +78 -0
- package/dist/ApexUI-Kit/templates/AiVoiceBlob/AiVoiceBlob.jsx +105 -0
- package/dist/ApexUI-Kit/templates/AiVoiceOrb/AiVoiceOrb.jsx +74 -0
- package/dist/ApexUI-Kit/templates/AiVoiceRing/AiVoiceRing.jsx +120 -0
- package/dist/ApexUI-Kit/templates/AiVoiceWave/AiVoiceWave.jsx +107 -0
- package/dist/ApexUI-Kit/templates/AnimatedSwitch/AnimatedSwitch.jsx +80 -0
- package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +10 -2
- package/dist/ApexUI-Kit/templates/AuroraBackground/AuroraBackground.jsx +48 -0
- package/dist/ApexUI-Kit/templates/BounceCards/BounceCards.jsx +53 -0
- package/dist/ApexUI-Kit/templates/CardSwap3D/CardSwap3D.jsx +80 -0
- package/dist/ApexUI-Kit/templates/CircularText/CircularText.jsx +49 -0
- package/dist/ApexUI-Kit/templates/ClickSpark/ClickSpark.jsx +72 -0
- package/dist/ApexUI-Kit/templates/Coverflow3D/Coverflow3D.jsx +108 -0
- package/dist/ApexUI-Kit/templates/CubeGallery/CubeGallery.jsx +166 -0
- package/dist/ApexUI-Kit/templates/CursorTrail/CursorTrail.jsx +200 -0
- package/dist/ApexUI-Kit/templates/ElasticSlider/ElasticSlider.jsx +152 -0
- package/dist/ApexUI-Kit/templates/ExtrudedText/ExtrudedText.jsx +78 -0
- package/dist/ApexUI-Kit/templates/FlipBook/FlipBook.jsx +191 -0
- package/dist/ApexUI-Kit/templates/FloatingNavbar/FloatingNavbar.jsx +162 -0
- package/dist/ApexUI-Kit/templates/Folder/Folder.jsx +101 -0
- package/dist/ApexUI-Kit/templates/GlitchText/GlitchText.jsx +56 -0
- package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +310 -0
- package/dist/ApexUI-Kit/templates/HoldToConfirm/HoldToConfirm.jsx +169 -0
- package/dist/ApexUI-Kit/templates/HoldToConfirm/UndoNotice.jsx +58 -0
- package/dist/ApexUI-Kit/templates/HoloCard/HoloCard.jsx +92 -0
- package/dist/ApexUI-Kit/templates/ImageCompare/ImageCompare.jsx +94 -0
- package/dist/ApexUI-Kit/templates/IsoStack/IsoStack.jsx +86 -0
- package/dist/ApexUI-Kit/templates/LoadingScreen/LoadingScreen.jsx +545 -101
- package/dist/ApexUI-Kit/templates/MagnetLines/MagnetLines.jsx +67 -0
- package/dist/ApexUI-Kit/templates/MagneticCursor/MagneticCursor.jsx +2 -160
- package/dist/ApexUI-Kit/templates/PixelTransition/PixelTransition.jsx +75 -0
- package/dist/ApexUI-Kit/templates/PricingCards/PricingCards.jsx +115 -0
- package/dist/ApexUI-Kit/templates/ProximityText/ProximityText.jsx +66 -0
- package/dist/ApexUI-Kit/templates/RotatingText/RotatingText.jsx +50 -0
- package/dist/ApexUI-Kit/templates/ShinyText/ShinyText.jsx +30 -0
- package/dist/ApexUI-Kit/templates/StarBorder/StarBorder.jsx +33 -0
- package/dist/ApexUI-Kit/templates/Stepper/Stepper.jsx +129 -0
- package/dist/ApexUI-Kit/templates/SwipeCards/SwipeCards.jsx +123 -0
- package/dist/ApexUI-Kit/templates/TextRing3D/TextRing3D.jsx +73 -0
- package/dist/ApexUI-Kit/templates/TrueFocus/TrueFocus.jsx +64 -0
- package/dist/ApexUI-Kit/templates/TypewriterText/TypewriterText.jsx +70 -0
- package/dist/cli/index.js +43 -0
- package/package.json +1 -1
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { AnimatePresence, motion } from "framer-motion";
|
|
3
|
+
import { Mic, MicOff } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* AI Voice Wave: layered glowing sine waves, like a phone assistant. Flat
|
|
7
|
+
* and calm when idle, lively while listening, a slow swell while thinking
|
|
8
|
+
* and full, syllable-like bursts while speaking. Tap to cycle the states or
|
|
9
|
+
* control `state` yourself.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const LABELS = { idle: "Tap to talk", listening: "Listening…", thinking: "Thinking…", speaking: "Speaking…" };
|
|
13
|
+
const ORDER = ["idle", "listening", "thinking", "speaking"];
|
|
14
|
+
|
|
15
|
+
// How loud the voice "is" at time t for each state (0–1).
|
|
16
|
+
const levelAt = (state, t) =>
|
|
17
|
+
state === "speaking" ? 0.45 + 0.55 * Math.abs(Math.sin(t * 8.5) * Math.sin(t * 2.1)) : state === "listening" ? 0.35 + 0.45 * Math.abs(Math.sin(t * 5.2) * Math.cos(t * 1.6)) : state === "thinking" ? 0.22 + 0.1 * Math.sin(t * 2) : 0.06;
|
|
18
|
+
|
|
19
|
+
const AiVoiceWave = ({ state: stateProp, onStateChange, colors = ["#a3e635", "#22d3ee", "#a78bfa"], height = 9, showLabel = true, className = "" }) => {
|
|
20
|
+
const canvasRef = useRef(null);
|
|
21
|
+
const [inner, setInner] = useState("idle");
|
|
22
|
+
const state = stateProp ?? inner;
|
|
23
|
+
const stateRef = useRef(state);
|
|
24
|
+
stateRef.current = state;
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
const canvas = canvasRef.current;
|
|
28
|
+
const ctx = canvas.getContext("2d");
|
|
29
|
+
let raf = 0;
|
|
30
|
+
let level = 0.05;
|
|
31
|
+
const resize = () => {
|
|
32
|
+
const r = canvas.getBoundingClientRect();
|
|
33
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
34
|
+
canvas.width = Math.round(r.width * dpr);
|
|
35
|
+
canvas.height = Math.round(r.height * dpr);
|
|
36
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
37
|
+
};
|
|
38
|
+
const ro = new ResizeObserver(resize);
|
|
39
|
+
ro.observe(canvas);
|
|
40
|
+
resize();
|
|
41
|
+
|
|
42
|
+
const draw = (now) => {
|
|
43
|
+
const t = now / 1000;
|
|
44
|
+
const { width: w, height: h } = canvas.getBoundingClientRect();
|
|
45
|
+
level += (levelAt(stateRef.current, t) - level) * 0.12;
|
|
46
|
+
ctx.clearRect(0, 0, w, h);
|
|
47
|
+
colors.forEach((c, k) => {
|
|
48
|
+
const amp = h * 0.42 * level * (1 - k * 0.18);
|
|
49
|
+
const freq = 1.6 + k * 0.7;
|
|
50
|
+
const phase = t * (2.2 + k * 0.9) * (stateRef.current === "thinking" ? 0.5 : 1);
|
|
51
|
+
ctx.beginPath();
|
|
52
|
+
for (let x = 0; x <= w; x += 2) {
|
|
53
|
+
const u = x / w;
|
|
54
|
+
// Taper both ends so the wave fades in from a flat line.
|
|
55
|
+
const taper = Math.pow(Math.sin(Math.PI * u), 2.2);
|
|
56
|
+
const y = h / 2 + Math.sin(u * Math.PI * 2 * freq + phase) * amp * taper;
|
|
57
|
+
if (x === 0) ctx.moveTo(x, y);
|
|
58
|
+
else ctx.lineTo(x, y);
|
|
59
|
+
}
|
|
60
|
+
ctx.strokeStyle = c;
|
|
61
|
+
ctx.lineWidth = 3 - k * 0.5;
|
|
62
|
+
ctx.shadowColor = c;
|
|
63
|
+
ctx.shadowBlur = 14;
|
|
64
|
+
ctx.globalAlpha = 0.85;
|
|
65
|
+
ctx.stroke();
|
|
66
|
+
});
|
|
67
|
+
ctx.globalAlpha = 1;
|
|
68
|
+
ctx.shadowBlur = 0;
|
|
69
|
+
raf = requestAnimationFrame(draw);
|
|
70
|
+
};
|
|
71
|
+
raf = requestAnimationFrame(draw);
|
|
72
|
+
return () => {
|
|
73
|
+
cancelAnimationFrame(raf);
|
|
74
|
+
ro.disconnect();
|
|
75
|
+
};
|
|
76
|
+
}, [colors]);
|
|
77
|
+
|
|
78
|
+
const next = () => {
|
|
79
|
+
const n = ORDER[(ORDER.indexOf(state) + 1) % ORDER.length];
|
|
80
|
+
if (stateProp === undefined) setInner(n);
|
|
81
|
+
onStateChange?.(n);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className={`flex w-full max-w-xl flex-col items-center gap-4 ${className}`}>
|
|
86
|
+
<button type="button" onClick={next} aria-label={`Voice assistant: ${LABELS[state]}`} className="w-full rounded-3xl outline-none focus-visible:ring-2 focus-visible:ring-white/30">
|
|
87
|
+
<canvas ref={canvasRef} aria-hidden="true" className="block w-full" style={{ height: `${height}rem` }} />
|
|
88
|
+
</button>
|
|
89
|
+
{showLabel && <Label state={state} color={colors[0]} />}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
function Label({ state, color }) {
|
|
95
|
+
return (
|
|
96
|
+
<div className="flex items-center gap-2 text-sm font-medium">
|
|
97
|
+
{state === "idle" ? <MicOff className="h-4 w-4 opacity-60" /> : <Mic className="h-4 w-4" style={{ color }} />}
|
|
98
|
+
<AnimatePresence mode="wait">
|
|
99
|
+
<motion.span key={state} initial={{ opacity: 0, y: 6 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: -6 }} transition={{ duration: 0.18 }}>
|
|
100
|
+
{LABELS[state]}
|
|
101
|
+
</motion.span>
|
|
102
|
+
</AnimatePresence>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default AiVoiceWave;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useId, useState } from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
import { Check, Moon, Sun, X } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Animated Switch: a toggle with personality. Variants:
|
|
7
|
+
* classic – the familiar pill with a springy knob that stretches as it moves
|
|
8
|
+
* icon – the knob shows a cross or a check
|
|
9
|
+
* daynight – a sun crosses into a starry night
|
|
10
|
+
* neon – a glowing track that lights up when on
|
|
11
|
+
* Works controlled (`checked` + `onChange`) or on its own (`defaultChecked`).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const knobSpring = { type: "spring", stiffness: 520, damping: 32 };
|
|
15
|
+
|
|
16
|
+
const AnimatedSwitch = ({ variant = "classic", checked, defaultChecked = false, onChange, label, accentColor = "#a3e635", size = 1, disabled = false, className = "" }) => {
|
|
17
|
+
const id = useId();
|
|
18
|
+
const [inner, setInner] = useState(defaultChecked);
|
|
19
|
+
const on = checked ?? inner;
|
|
20
|
+
const toggle = () => {
|
|
21
|
+
if (disabled) return;
|
|
22
|
+
if (checked === undefined) setInner(!on);
|
|
23
|
+
onChange?.(!on);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const W = 3.25 * size;
|
|
27
|
+
const H = 1.85 * size;
|
|
28
|
+
const K = H - 0.35 * size;
|
|
29
|
+
const travel = W - K - 0.35 * size;
|
|
30
|
+
|
|
31
|
+
const track = {
|
|
32
|
+
classic: { background: on ? accentColor : "rgba(120,120,128,0.32)" },
|
|
33
|
+
icon: { background: on ? accentColor : "rgba(120,120,128,0.32)" },
|
|
34
|
+
daynight: { background: on ? "linear-gradient(135deg,#0f172a,#312e81)" : "linear-gradient(135deg,#7dd3fc,#38bdf8)" },
|
|
35
|
+
neon: { background: "#0a0a0a", boxShadow: on ? `0 0 0 1.5px ${accentColor}, 0 0 18px ${accentColor}99, inset 0 0 12px ${accentColor}55` : "0 0 0 1.5px rgba(255,255,255,0.15)" },
|
|
36
|
+
}[variant];
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<label htmlFor={id} className={`inline-flex select-none items-center gap-3 ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"} ${className}`}>
|
|
40
|
+
<button
|
|
41
|
+
id={id}
|
|
42
|
+
type="button"
|
|
43
|
+
role="switch"
|
|
44
|
+
aria-checked={on}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
onClick={toggle}
|
|
47
|
+
className="relative shrink-0 overflow-hidden rounded-full outline-none transition-[background,box-shadow] duration-300 focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
48
|
+
style={{ width: `${W}rem`, height: `${H}rem`, ...track }}
|
|
49
|
+
>
|
|
50
|
+
{variant === "daynight" && (
|
|
51
|
+
<>
|
|
52
|
+
{[0.9, 1.6, 2.2, 1.2].map((x, i) => (
|
|
53
|
+
<motion.span
|
|
54
|
+
key={i}
|
|
55
|
+
className="absolute h-[0.14rem] w-[0.14rem] rounded-full bg-white"
|
|
56
|
+
style={{ left: `${x * size * 0.7}rem`, top: `${(0.35 + (i % 2) * 0.55) * size}rem` }}
|
|
57
|
+
animate={{ opacity: on ? [0.3, 1, 0.3] : 0, scale: on ? 1 : 0 }}
|
|
58
|
+
transition={{ duration: 1.6, repeat: on ? Infinity : 0, delay: i * 0.3 }}
|
|
59
|
+
/>
|
|
60
|
+
))}
|
|
61
|
+
<motion.span className="absolute rounded-full bg-white/80" style={{ width: `${0.9 * size}rem`, height: `${0.35 * size}rem`, top: `${1.05 * size}rem` }} animate={{ left: on ? `${-1.2 * size}rem` : `${1.7 * size}rem`, opacity: on ? 0 : 1 }} transition={{ duration: 0.4 }} />
|
|
62
|
+
</>
|
|
63
|
+
)}
|
|
64
|
+
<motion.span
|
|
65
|
+
className="absolute grid place-items-center rounded-full shadow-[0_2px_6px_rgba(0,0,0,0.3)]"
|
|
66
|
+
style={{ left: `${0.175 * size}rem`, top: `${(H - K) / 2}rem`, height: `${K}rem`, background: variant === "daynight" ? (on ? "#e2e8f0" : "#fde047") : variant === "neon" ? (on ? accentColor : "#3f3f46") : "#ffffff" }}
|
|
67
|
+
initial={false}
|
|
68
|
+
animate={{ x: on ? `${travel}rem` : "0rem", width: on ? [`${K}rem`, `${K * 1.25}rem`, `${K}rem`] : [`${K}rem`, `${K * 1.24}rem`, `${K}rem`] }}
|
|
69
|
+
transition={{ x: knobSpring, width: { duration: 0.3 } }}
|
|
70
|
+
>
|
|
71
|
+
{variant === "icon" && (on ? <Check className="h-3/5 w-3/5" style={{ color: accentColor }} strokeWidth={3} /> : <X className="h-3/5 w-3/5 text-neutral-400" strokeWidth={3} />)}
|
|
72
|
+
{variant === "daynight" && (on ? <Moon className="h-3/5 w-3/5 text-slate-500" fill="currentColor" /> : <Sun className="h-3/5 w-3/5 text-amber-600" />)}
|
|
73
|
+
</motion.span>
|
|
74
|
+
</button>
|
|
75
|
+
{label && <span className="text-sm font-medium">{label}</span>}
|
|
76
|
+
</label>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default AnimatedSwitch;
|
|
@@ -76,7 +76,15 @@ const AnimatedTabs = ({ tabs = DEFAULT_TABS, defaultTab, variant = "pill", accen
|
|
|
76
76
|
);
|
|
77
77
|
})}
|
|
78
78
|
</div>
|
|
79
|
-
<div id={`${id}-panel`} role="tabpanel" aria-labelledby={`${id}-tab-${active}`} className="relative mt-4 min-h-[6rem] overflow-hidden">
|
|
79
|
+
<div id={`${id}-panel`} role="tabpanel" aria-labelledby={`${id}-tab-${active}`} className="relative mt-4 grid min-h-[6rem] overflow-hidden">
|
|
80
|
+
{/* Invisible copies of every panel reserve the tallest height, so the
|
|
81
|
+
tab bar never jumps when panels of different lengths swap. */}
|
|
82
|
+
{tabs.map((t) => (
|
|
83
|
+
<div key={t.id} aria-hidden="true" className="invisible col-start-1 row-start-1 p-5 text-[0.92rem] leading-relaxed">
|
|
84
|
+
<p className="mb-1.5 text-[1rem] font-semibold">{t.label}</p>
|
|
85
|
+
{t.content}
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
80
88
|
<AnimatePresence mode="popLayout" initial={false} custom={dir}>
|
|
81
89
|
<motion.div
|
|
82
90
|
key={active}
|
|
@@ -85,7 +93,7 @@ const AnimatedTabs = ({ tabs = DEFAULT_TABS, defaultTab, variant = "pill", accen
|
|
|
85
93
|
animate={{ opacity: 1, x: 0, filter: "blur(0px)" }}
|
|
86
94
|
exit={{ opacity: 0, x: dir * -24, filter: "blur(4px)" }}
|
|
87
95
|
transition={{ duration: 0.28, ease: [0.22, 1, 0.36, 1] }}
|
|
88
|
-
className={`rounded-2xl p-5 text-[0.92rem] leading-relaxed ${dark ? "bg-white/[0.04] text-white/75" : "bg-black/[0.03] text-neutral-600"}`}
|
|
96
|
+
className={`col-start-1 row-start-1 h-full rounded-2xl p-5 text-[0.92rem] leading-relaxed ${dark ? "bg-white/[0.04] text-white/75" : "bg-black/[0.03] text-neutral-600"}`}
|
|
89
97
|
>
|
|
90
98
|
<p className={`mb-1.5 text-[1rem] font-semibold ${dark ? "text-white" : "text-neutral-900"}`}>{current?.label}</p>
|
|
91
99
|
{current?.content}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Aurora Background: soft northern-light bands that drift and shimmer, built
|
|
5
|
+
* from blurred CSS gradients only (no canvas, no WebGL). Works on light and
|
|
6
|
+
* dark pages; reduced-motion visitors get the same picture without movement.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const AuroraBackground = ({
|
|
10
|
+
children,
|
|
11
|
+
colors = ["#22d3ee", "#a3e635", "#a78bfa", "#38bdf8"],
|
|
12
|
+
theme = "dark",
|
|
13
|
+
speed = 1,
|
|
14
|
+
intensity = 1,
|
|
15
|
+
className = "",
|
|
16
|
+
containerClassName = "",
|
|
17
|
+
}) => {
|
|
18
|
+
const dark = theme !== "light";
|
|
19
|
+
const stops = colors.map((c, i) => `${c} ${Math.round((i / colors.length) * 100)}%`).join(", ");
|
|
20
|
+
const duration = `${60 / Math.max(0.1, speed)}s`;
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div className={`relative h-screen w-full overflow-hidden ${dark ? "bg-[#05060a]" : "bg-[#f8fafc]"} ${containerClassName}`}>
|
|
24
|
+
<style>{`
|
|
25
|
+
@keyframes apex-aurora { from { background-position: 50% 50%, 50% 50%; } to { background-position: 350% 50%, 350% 50%; } }
|
|
26
|
+
@media (prefers-reduced-motion: reduce) { .apex-aurora, .apex-aurora::after { animation: none !important; } }
|
|
27
|
+
`}</style>
|
|
28
|
+
<div
|
|
29
|
+
aria-hidden="true"
|
|
30
|
+
className="apex-aurora pointer-events-none absolute -inset-[10px] will-change-transform after:absolute after:inset-0 after:content-['']"
|
|
31
|
+
style={{
|
|
32
|
+
opacity: Math.min(1, (dark ? 0.9 : 0.75) * intensity),
|
|
33
|
+
filter: `blur(${dark ? 12 : 10}px) ${dark ? "" : "invert(0)"}`,
|
|
34
|
+
backgroundImage: `repeating-linear-gradient(100deg, ${dark ? "#000" : "#fff"} 0%, ${dark ? "#000" : "#fff"} 7%, transparent 10%, transparent 12%, ${dark ? "#000" : "#fff"} 16%), repeating-linear-gradient(100deg, ${stops}, ${colors[0]} 100%)`,
|
|
35
|
+
backgroundSize: "300%, 200%",
|
|
36
|
+
backgroundPosition: "50% 50%, 50% 50%",
|
|
37
|
+
maskImage: "radial-gradient(ellipse 120% 95% at 70% 0%, black 25%, transparent 78%)",
|
|
38
|
+
WebkitMaskImage: "radial-gradient(ellipse 120% 95% at 70% 0%, black 25%, transparent 78%)",
|
|
39
|
+
animation: `apex-aurora ${duration} linear infinite`,
|
|
40
|
+
mixBlendMode: dark ? "normal" : "multiply",
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
{children && <div className={`relative z-10 h-full w-full ${className}`}>{children}</div>}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default AuroraBackground;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Bounce Cards: a hand of photo cards that bounce into a fan on mount.
|
|
6
|
+
* Hover one and it straightens and lifts while its neighbours lean away to
|
|
7
|
+
* make room; move off and everything settles back into the fan.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const IK = "https://ik.imagekit.io/ny6yinyut/apexui/assets";
|
|
11
|
+
const img = (n) => `${IK}/${n}.png?tr=f-auto,q-80,w-400`;
|
|
12
|
+
const DEFAULT_IMAGES = [img("hulk"), img("captainamerica"), img("spiderman"), img("doctorStrange"), img("black-panther")];
|
|
13
|
+
|
|
14
|
+
const BounceCards = ({ images = DEFAULT_IMAGES, cardSize = 9, spread = 5.5, fan = 8, delay = 0.2, className = "" }) => {
|
|
15
|
+
const [hovered, setHovered] = useState(null);
|
|
16
|
+
const mid = (images.length - 1) / 2;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className={`relative flex items-center justify-center ${className}`} style={{ width: `min(${cardSize + spread * (images.length - 1) + 4}rem, 92vw)`, height: `${cardSize * 1.5}rem` }} onPointerLeave={() => setHovered(null)}>
|
|
20
|
+
{images.map((src, i) => {
|
|
21
|
+
const off = i - mid;
|
|
22
|
+
let x = off * spread;
|
|
23
|
+
let rotate = off * fan;
|
|
24
|
+
let y = Math.abs(off) * 0.6;
|
|
25
|
+
if (hovered !== null) {
|
|
26
|
+
if (i === hovered) {
|
|
27
|
+
rotate = 0;
|
|
28
|
+
y = -1;
|
|
29
|
+
} else {
|
|
30
|
+
// Neighbours lean away, closer ones more.
|
|
31
|
+
const d = i - hovered;
|
|
32
|
+
x += Math.sign(d) * Math.max(0, 3 - Math.abs(d) * 0.8);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return (
|
|
36
|
+
<motion.div
|
|
37
|
+
key={i}
|
|
38
|
+
className="absolute overflow-hidden rounded-[1.4rem] border-[0.35rem] border-white bg-white shadow-[0_18px_40px_-18px_rgba(0,0,0,0.55)]"
|
|
39
|
+
style={{ width: `${cardSize}rem`, height: `${cardSize}rem`, zIndex: i === hovered ? 50 : 10 - Math.abs(Math.round(off)) }}
|
|
40
|
+
initial={{ scale: 0, x: 0, y: "0rem", rotate: 0 }}
|
|
41
|
+
animate={{ scale: i === hovered ? 1.06 : 1, x: `${x}rem`, y: `${y}rem`, rotate }}
|
|
42
|
+
transition={{ type: "spring", stiffness: 260, damping: 14, delay: hovered === null ? delay + i * 0.06 : 0 }}
|
|
43
|
+
onPointerEnter={() => setHovered(i)}
|
|
44
|
+
>
|
|
45
|
+
<img src={src} alt="" draggable="false" className="h-full w-full bg-neutral-200 object-cover" />
|
|
46
|
+
</motion.div>
|
|
47
|
+
);
|
|
48
|
+
})}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default BounceCards;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
import { Code2, Layers, Sparkles, Zap } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Card Swap 3D: cards stacked diagonally into the distance in 3D. On a
|
|
7
|
+
* timer the front card drops away and slides to the back of the stack
|
|
8
|
+
* while the rest step forward. Pauses while hovered; click to swap now.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const DEFAULT_CARDS = [
|
|
12
|
+
{ icon: Sparkles, title: "Smooth", body: "Springs tuned to feel natural, never floaty." },
|
|
13
|
+
{ icon: Layers, title: "Composable", body: "Drop components in and style them with Tailwind." },
|
|
14
|
+
{ icon: Code2, title: "Yours", body: "Copy the source or add it with one CLI command." },
|
|
15
|
+
{ icon: Zap, title: "Fast", body: "Transforms and opacity only — 60fps on phones." },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const CardSwap3D = ({ cards = DEFAULT_CARDS, delay = 3200, distanceX = 3.6, distanceY = 3.2, skew = 5, pauseOnHover = true, accentColor = "#a3e635", theme = "dark", width = 18, className = "" }) => {
|
|
19
|
+
// order[0] is the card in front.
|
|
20
|
+
const [order, setOrder] = useState(() => cards.map((_, i) => i));
|
|
21
|
+
const [dropping, setDropping] = useState(null);
|
|
22
|
+
const [paused, setPaused] = useState(false);
|
|
23
|
+
const dark = theme !== "light";
|
|
24
|
+
|
|
25
|
+
const swap = () => {
|
|
26
|
+
if (dropping !== null) return;
|
|
27
|
+
setDropping(order[0]);
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
setOrder((o) => [...o.slice(1), o[0]]);
|
|
30
|
+
setDropping(null);
|
|
31
|
+
}, 520);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (paused) return undefined;
|
|
36
|
+
const id = setInterval(swap, delay);
|
|
37
|
+
return () => clearInterval(id);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={`relative [perspective:1000px] ${className}`}
|
|
43
|
+
style={{ width: `min(${width}rem, 70vw)`, height: `${width * 0.68}rem`, marginTop: `${distanceY * (cards.length - 1)}rem`, marginRight: `${distanceX * (cards.length - 1)}rem` }}
|
|
44
|
+
onPointerEnter={() => pauseOnHover && setPaused(true)}
|
|
45
|
+
onPointerLeave={() => setPaused(false)}
|
|
46
|
+
onClick={swap}
|
|
47
|
+
>
|
|
48
|
+
{cards.map((card, i) => {
|
|
49
|
+
const slot = order.indexOf(i);
|
|
50
|
+
const isDropping = dropping === i;
|
|
51
|
+
const Icon = card.icon ?? Sparkles;
|
|
52
|
+
return (
|
|
53
|
+
<motion.div
|
|
54
|
+
key={i}
|
|
55
|
+
className={`absolute inset-0 cursor-pointer overflow-hidden rounded-2xl border p-6 [backface-visibility:hidden] ${dark ? "border-white/10 bg-neutral-950 text-white" : "border-black/10 bg-white text-neutral-900"}`}
|
|
56
|
+
style={{ zIndex: isDropping ? 60 : cards.length - slot, transformStyle: "preserve-3d", boxShadow: "0 25px 50px -25px rgba(0,0,0,0.6)" }}
|
|
57
|
+
initial={false}
|
|
58
|
+
animate={
|
|
59
|
+
isDropping
|
|
60
|
+
? { x: "0rem", y: "14rem", z: 0, skewY: skew, opacity: 0.0 }
|
|
61
|
+
: { x: `${slot * distanceX}rem`, y: `${-slot * distanceY}rem`, z: -slot * 60, skewY: skew, opacity: slot > 4 ? 0 : 1 }
|
|
62
|
+
}
|
|
63
|
+
transition={isDropping ? { duration: 0.5, ease: [0.55, 0, 0.8, 0.2] } : { type: "spring", stiffness: 140, damping: 20 }}
|
|
64
|
+
>
|
|
65
|
+
<div className="flex items-center gap-2.5">
|
|
66
|
+
<span className="grid h-9 w-9 place-items-center rounded-xl text-black" style={{ background: accentColor }}>
|
|
67
|
+
<Icon className="h-4.5 w-4.5" />
|
|
68
|
+
</span>
|
|
69
|
+
<p className="text-lg font-semibold">{card.title}</p>
|
|
70
|
+
</div>
|
|
71
|
+
<p className={`mt-3 text-[0.92rem] leading-relaxed ${dark ? "text-white/60" : "text-neutral-500"}`}>{card.body}</p>
|
|
72
|
+
<div className="pointer-events-none absolute -bottom-10 -right-10 h-32 w-32 rounded-full opacity-20 blur-2xl" style={{ background: accentColor }} />
|
|
73
|
+
</motion.div>
|
|
74
|
+
);
|
|
75
|
+
})}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default CardSwap3D;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useRef } from "react";
|
|
2
|
+
import { useAnimationFrame, useReducedMotion } from "framer-motion";
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* Circular Text: a line of text wrapped around a circle that keeps turning.
|
|
6
|
+
* On hover it can speed up, slow down, pause or spin wildly. Put anything in
|
|
7
|
+
* the middle with children.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const HOVER_SPEED = { speedUp: 4, slowDown: 0.25, pause: 0, goBonkers: 14, none: 1 };
|
|
11
|
+
|
|
12
|
+
const CircularText = ({ text = "APEX UI • MOTION • COMPONENTS • ", size = 11, duration = 18, onHover = "speedUp", color = "currentColor", children, className = "" }) => {
|
|
13
|
+
const reduced = useReducedMotion();
|
|
14
|
+
const ring = useRef(null);
|
|
15
|
+
const angle = useRef(0);
|
|
16
|
+
const speed = useRef(1);
|
|
17
|
+
const target = useRef(1);
|
|
18
|
+
const chars = Array.from(text);
|
|
19
|
+
|
|
20
|
+
useAnimationFrame((_, delta) => {
|
|
21
|
+
if (reduced || !ring.current) return;
|
|
22
|
+
speed.current += (target.current - speed.current) * 0.06;
|
|
23
|
+
angle.current = (angle.current + (360 / duration) * (delta / 1000) * speed.current) % 360;
|
|
24
|
+
const s = onHover === "goBonkers" && target.current > 1 ? 1 + Math.min(0.12, (speed.current - 1) * 0.01) : 1;
|
|
25
|
+
ring.current.style.transform = `rotate(${angle.current}deg) scale(${s})`;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className={`relative grid place-items-center rounded-full ${className}`}
|
|
31
|
+
style={{ width: `${size}rem`, height: `${size}rem`, color }}
|
|
32
|
+
onPointerEnter={() => (target.current = HOVER_SPEED[onHover] ?? 1)}
|
|
33
|
+
onPointerLeave={() => (target.current = 1)}
|
|
34
|
+
aria-label={text}
|
|
35
|
+
role="img"
|
|
36
|
+
>
|
|
37
|
+
<div ref={ring} className="absolute inset-0" aria-hidden="true">
|
|
38
|
+
{chars.map((c, i) => (
|
|
39
|
+
<span key={i} className="absolute left-1/2 top-0 h-1/2 origin-bottom text-[0.95rem] font-bold uppercase" style={{ transform: `translateX(-50%) rotate(${(i / chars.length) * 360}deg)` }}>
|
|
40
|
+
{c}
|
|
41
|
+
</span>
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
{children && <div className="relative">{children}</div>}
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default CircularText;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* Click Spark: wrap anything and every click throws out a ring of short
|
|
5
|
+
* spark lines from the pointer that shoot out and fade. Drawn on a canvas
|
|
6
|
+
* overlay so it never blocks clicks underneath.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const ease = (t) => t * (2 - t);
|
|
10
|
+
|
|
11
|
+
const ClickSpark = ({ sparkColor = "#a3e635", sparkCount = 10, sparkRadius = 26, sparkSize = 12, duration = 450, children, className = "" }) => {
|
|
12
|
+
const wrap = useRef(null);
|
|
13
|
+
const canvas = useRef(null);
|
|
14
|
+
const sparks = useRef([]);
|
|
15
|
+
const raf = useRef(0);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const c = canvas.current;
|
|
19
|
+
const resize = () => {
|
|
20
|
+
const r = wrap.current.getBoundingClientRect();
|
|
21
|
+
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
22
|
+
c.width = r.width * dpr;
|
|
23
|
+
c.height = r.height * dpr;
|
|
24
|
+
c.getContext("2d").setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25
|
+
};
|
|
26
|
+
const ro = new ResizeObserver(resize);
|
|
27
|
+
ro.observe(wrap.current);
|
|
28
|
+
resize();
|
|
29
|
+
return () => {
|
|
30
|
+
ro.disconnect();
|
|
31
|
+
cancelAnimationFrame(raf.current);
|
|
32
|
+
};
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const draw = () => {
|
|
36
|
+
const ctx = canvas.current.getContext("2d");
|
|
37
|
+
const now = performance.now();
|
|
38
|
+
ctx.clearRect(0, 0, canvas.current.width, canvas.current.height);
|
|
39
|
+
sparks.current = sparks.current.filter((s) => now - s.start < duration);
|
|
40
|
+
for (const s of sparks.current) {
|
|
41
|
+
const p = ease((now - s.start) / duration);
|
|
42
|
+
const dist = p * sparkRadius;
|
|
43
|
+
const len = sparkSize * (1 - p);
|
|
44
|
+
ctx.strokeStyle = sparkColor;
|
|
45
|
+
ctx.lineWidth = 2;
|
|
46
|
+
ctx.lineCap = "round";
|
|
47
|
+
ctx.beginPath();
|
|
48
|
+
ctx.moveTo(s.x + dist * Math.cos(s.a), s.y + dist * Math.sin(s.a));
|
|
49
|
+
ctx.lineTo(s.x + (dist + len) * Math.cos(s.a), s.y + (dist + len) * Math.sin(s.a));
|
|
50
|
+
ctx.stroke();
|
|
51
|
+
}
|
|
52
|
+
raf.current = sparks.current.length ? requestAnimationFrame(draw) : 0;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onClick = (e) => {
|
|
56
|
+
const r = wrap.current.getBoundingClientRect();
|
|
57
|
+
const x = e.clientX - r.left;
|
|
58
|
+
const y = e.clientY - r.top;
|
|
59
|
+
const start = performance.now();
|
|
60
|
+
for (let i = 0; i < sparkCount; i++) sparks.current.push({ x, y, a: (Math.PI * 2 * i) / sparkCount, start });
|
|
61
|
+
if (!raf.current) raf.current = requestAnimationFrame(draw);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div ref={wrap} className={`relative ${className}`} onClick={onClick}>
|
|
66
|
+
{children}
|
|
67
|
+
<canvas ref={canvas} aria-hidden="true" className="pointer-events-none absolute inset-0 h-full w-full" />
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export default ClickSpark;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import React, { useRef, useState } from "react";
|
|
2
|
+
import { motion } from "framer-motion";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Coverflow 3D: the classic album-flip view. The centre cover faces you,
|
|
7
|
+
* the rest stand angled on either side and slide back in depth, each with a
|
|
8
|
+
* soft reflection. Drag / swipe, click a cover, or use the arrow keys.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const IK = "https://ik.imagekit.io/ny6yinyut/apexui/assets";
|
|
12
|
+
const img = (n) => `${IK}/${n}.png?tr=f-auto,q-80,w-500`;
|
|
13
|
+
const DEFAULT_ITEMS = [
|
|
14
|
+
{ image: img("spiderman"), title: "Spider-Man" },
|
|
15
|
+
{ image: img("black-panther"), title: "Black Panther" },
|
|
16
|
+
{ image: img("hulk"), title: "Hulk" },
|
|
17
|
+
{ image: img("captainamerica"), title: "Captain America" },
|
|
18
|
+
{ image: img("doctorStrange"), title: "Doctor Strange" },
|
|
19
|
+
{ image: img("thanos"), title: "Thanos" },
|
|
20
|
+
{ image: img("ironman"), title: "Iron Man" },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const Coverflow3D = ({ items = DEFAULT_ITEMS, initialIndex = 2, size = 12, angle = 55, gap = 0.42, reflection = true, accentColor = "#a3e635", className = "" }) => {
|
|
24
|
+
const [index, setIndex] = useState(initialIndex);
|
|
25
|
+
const drag = useRef(null);
|
|
26
|
+
const clamp = (i) => Math.max(0, Math.min(items.length - 1, i));
|
|
27
|
+
const go = (i) => setIndex(clamp(i));
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
className={`flex w-full flex-col items-center gap-6 outline-none ${className}`}
|
|
32
|
+
tabIndex={0}
|
|
33
|
+
onKeyDown={(e) => {
|
|
34
|
+
if (e.key === "ArrowRight") go(index + 1);
|
|
35
|
+
if (e.key === "ArrowLeft") go(index - 1);
|
|
36
|
+
}}
|
|
37
|
+
aria-roledescription="carousel"
|
|
38
|
+
aria-label="Coverflow"
|
|
39
|
+
>
|
|
40
|
+
<div
|
|
41
|
+
className="relative w-full touch-pan-y select-none overflow-hidden [perspective:1100px]"
|
|
42
|
+
style={{ height: `${size * (reflection ? 1.55 : 1.15)}rem` }}
|
|
43
|
+
onPointerDown={(e) => (drag.current = { x: e.clientX, start: index, moved: false })}
|
|
44
|
+
onPointerMove={(e) => {
|
|
45
|
+
const d = drag.current;
|
|
46
|
+
if (!d) return;
|
|
47
|
+
const steps = Math.round((d.x - e.clientX) / 70);
|
|
48
|
+
if (steps !== 0) d.moved = true;
|
|
49
|
+
go(d.start + steps);
|
|
50
|
+
}}
|
|
51
|
+
onPointerUp={() => setTimeout(() => (drag.current = null))}
|
|
52
|
+
onPointerLeave={() => (drag.current = null)}
|
|
53
|
+
>
|
|
54
|
+
{items.map((item, i) => {
|
|
55
|
+
const off = i - index;
|
|
56
|
+
const abs = Math.abs(off);
|
|
57
|
+
const side = Math.sign(off);
|
|
58
|
+
return (
|
|
59
|
+
<motion.button
|
|
60
|
+
key={i}
|
|
61
|
+
type="button"
|
|
62
|
+
aria-label={item.title}
|
|
63
|
+
aria-current={off === 0}
|
|
64
|
+
onClick={() => !drag.current?.moved && go(i)}
|
|
65
|
+
className="absolute left-1/2 top-[6%] [transform-style:preserve-3d]"
|
|
66
|
+
style={{ width: `${size}rem`, height: `${size}rem`, marginLeft: `-${size / 2}rem`, zIndex: 100 - abs }}
|
|
67
|
+
initial={false}
|
|
68
|
+
animate={{
|
|
69
|
+
x: off === 0 ? "0%" : `${side * (55 + (abs - 1) * gap * 100)}%`,
|
|
70
|
+
z: off === 0 ? 60 : -120 - abs * 20,
|
|
71
|
+
rotateY: off === 0 ? 0 : -side * angle,
|
|
72
|
+
opacity: abs > 4 ? 0 : 1,
|
|
73
|
+
}}
|
|
74
|
+
transition={{ type: "spring", stiffness: 220, damping: 30 }}
|
|
75
|
+
>
|
|
76
|
+
<span className="relative block h-full w-full overflow-hidden rounded-xl shadow-[0_25px_50px_-20px_rgba(0,0,0,0.7)] ring-1 ring-white/10">
|
|
77
|
+
<img src={item.image} alt="" draggable="false" className="h-full w-full bg-neutral-900 object-cover" />
|
|
78
|
+
<motion.span className="absolute inset-0 bg-black" initial={false} animate={{ opacity: off === 0 ? 0 : Math.min(0.55, 0.25 + abs * 0.1) }} />
|
|
79
|
+
</span>
|
|
80
|
+
{reflection && (
|
|
81
|
+
<span aria-hidden="true" className="pointer-events-none absolute left-0 top-full mt-1 block h-1/2 w-full overflow-hidden rounded-xl opacity-30 [mask-image:linear-gradient(to_bottom,black,transparent_70%)]">
|
|
82
|
+
<img src={item.image} alt="" draggable="false" className="h-[200%] w-full -scale-y-100 object-cover" />
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
</motion.button>
|
|
86
|
+
);
|
|
87
|
+
})}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="flex items-center gap-4">
|
|
91
|
+
<button type="button" aria-label="Previous" onClick={() => go(index - 1)} disabled={index === 0} className="grid h-10 w-10 place-items-center rounded-full border border-current/20 transition hover:scale-105 active:scale-95 disabled:opacity-30">
|
|
92
|
+
<ChevronLeft className="h-5 w-5" />
|
|
93
|
+
</button>
|
|
94
|
+
<p className="min-w-[9rem] text-center text-sm font-semibold">
|
|
95
|
+
{items[index].title}
|
|
96
|
+
<span className="mt-0.5 block font-mono text-[0.7rem] font-normal opacity-50">
|
|
97
|
+
{index + 1} / {items.length}
|
|
98
|
+
</span>
|
|
99
|
+
</p>
|
|
100
|
+
<button type="button" aria-label="Next" onClick={() => go(index + 1)} disabled={index === items.length - 1} className="grid h-10 w-10 place-items-center rounded-full border border-current/20 transition hover:scale-105 active:scale-95 disabled:opacity-30" style={{ color: index === items.length - 1 ? undefined : accentColor }}>
|
|
101
|
+
<ChevronRight className="h-5 w-5" />
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default Coverflow3D;
|