apex-ui-kit 2.7.0 → 2.9.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 (24) hide show
  1. package/ReadMe.md +21 -9
  2. package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +1 -1
  3. package/dist/ApexUI-Kit/templates/ArcGallery/ArcGallery.jsx +29 -11
  4. package/dist/ApexUI-Kit/templates/CircularCarousel/CircularCarousel.jsx +0 -1
  5. package/dist/ApexUI-Kit/templates/CommandPalette/CommandPalette.jsx +250 -0
  6. package/dist/ApexUI-Kit/templates/CrossedMarquee/CrossedMarquee.jsx +158 -0
  7. package/dist/ApexUI-Kit/templates/DitherWaves/DitherWaves.jsx +207 -0
  8. package/dist/ApexUI-Kit/templates/FlipClock/FlipClock.jsx +3 -2
  9. package/dist/ApexUI-Kit/templates/GlareCard/GlareCard.jsx +25 -18
  10. package/dist/ApexUI-Kit/templates/GlassLens/GlassLens.jsx +273 -0
  11. package/dist/ApexUI-Kit/templates/GooeyNav/GooeyNav.jsx +1 -1
  12. package/dist/ApexUI-Kit/templates/ImageAccordion/ImageAccordion.jsx +116 -0
  13. package/dist/ApexUI-Kit/templates/InfiniteCanvas/InfiniteCanvas.jsx +230 -0
  14. package/dist/ApexUI-Kit/templates/LiquidGlass/LiquidGlass.jsx +444 -0
  15. package/dist/ApexUI-Kit/templates/OrbitIcons/OrbitIcons.jsx +169 -0
  16. package/dist/ApexUI-Kit/templates/RadialMenu/RadialMenu.jsx +20 -4
  17. package/dist/ApexUI-Kit/templates/ScrollStack/ScrollStack.jsx +3 -3
  18. package/dist/ApexUI-Kit/templates/SunriseBackground/SunriseBackground.jsx +68 -2
  19. package/dist/ApexUI-Kit/templates/TextRing3D/TextRing3D.jsx +18 -3
  20. package/dist/ApexUI-Kit/templates/ThemeSwitcher/ThemeSwitcher.jsx +215 -0
  21. package/dist/ApexUI-Kit/templates/ThemeSwitcher/themes.js +38 -0
  22. package/dist/ApexUI-Kit/templates/ToastStack/ToastStack.jsx +21 -13
  23. package/dist/cli/index.js +10 -0
  24. package/package.json +2 -2
@@ -0,0 +1,158 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { siExpress, siJavascript, siMongodb, siNextdotjs, siNodedotjs, siReact, siRedux, siTailwindcss, siTypescript, siVite } from "simple-icons";
3
+
4
+ /*
5
+ * Crossed Marquee: two tilted bands crossing like an "X" and scrolling in
6
+ * opposite directions. The front band shows brand tiles and big labels, the
7
+ * back band a quieter label row. Scrolling the page bursts the bands forward
8
+ * (or reverses them when you scroll up), then they settle back to a cruise.
9
+ *
10
+ * Props: items (strings or { label, icon }), duration = 45 (s per loop),
11
+ * tilt = 3 (deg), accentColor, bandInk, backColor, tileColor, showBackBand,
12
+ * reactToScroll, pauseOnHover, className. Colours can be CSS variables.
13
+ */
14
+
15
+ const ICONS = { mongodb: siMongodb, expressjs: siExpress, express: siExpress, react: siReact, nodejs: siNodedotjs, javascript: siJavascript, redux: siRedux, tailwindcss: siTailwindcss, tailwind: siTailwindcss, vite: siVite, nextjs: siNextdotjs, typescript: siTypescript };
16
+ const DEFAULT_ITEMS = ["MongoDB", "Express.js", "React", "Node.js", "JavaScript", "Redux", "Tailwind CSS", "Vite"];
17
+
18
+ const keyOf = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, "");
19
+ const wrap = (v) => ((((v + 50) % 50) + 50) % 50) - 50;
20
+ const initials = (s) =>
21
+ String(s)
22
+ .split(/[\s.]+/)
23
+ .filter(Boolean)
24
+ .slice(0, 2)
25
+ .map((w) => w[0].toUpperCase())
26
+ .join("");
27
+
28
+ const normalise = (it) => (typeof it === "string" ? { label: it, icon: ICONS[keyOf(it)] } : { ...it, icon: it.icon ?? ICONS[keyOf(it.label)] });
29
+
30
+ const BrandIcon = ({ icon, label }) => {
31
+ if (icon && typeof icon === "object" && "path" in icon) {
32
+ return (
33
+ <svg viewBox="0 0 24 24" className="h-6 w-6 md:h-7 md:w-7" aria-hidden="true">
34
+ <path d={icon.path} fill={`#${icon.hex}`} />
35
+ </svg>
36
+ );
37
+ }
38
+ if (icon) return icon;
39
+ return <span className="text-sm font-black md:text-base">{initials(label)}</span>;
40
+ };
41
+
42
+ const CrossedMarquee = ({ items = DEFAULT_ITEMS, duration = 45, tilt = 3, accentColor = "#ff5b1f", bandInk = "#14110d", backColor = "#121110", tileColor = "#fffaf2", showBackBand = true, reactToScroll = true, pauseOnHover = false, className = "" }) => {
43
+ const root = useRef(null);
44
+ const front = useRef(null);
45
+ const back = useRef(null);
46
+ const live = useRef({});
47
+ live.current = { duration, reactToScroll, pauseOnHover };
48
+ const list = (items.length ? items : DEFAULT_ITEMS).map(normalise);
49
+ // Repeat the list so one copy is always wider than the screen.
50
+ const row = Array.from({ length: Math.max(2, Math.ceil(12 / list.length)) }, () => list).flat();
51
+
52
+ useEffect(() => {
53
+ const el = root.current;
54
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
55
+ if (reduced) return undefined;
56
+ const s = { a: 0, b: -25, dir: 1, speed: 1, lastY: window.scrollY, lastT: performance.now(), hover: false };
57
+ let raf = 0;
58
+ let last = 0;
59
+ let visible = true;
60
+
61
+ const frame = (now) => {
62
+ raf = 0;
63
+ const o = live.current;
64
+ const dt = last ? Math.min(0.05, (now - last) / 1000) : 1 / 60;
65
+ last = now;
66
+ s.speed += (s.dir - s.speed) * 0.05;
67
+ const k = o.pauseOnHover && s.hover ? 0 : 1;
68
+ const d = Math.max(1, o.duration);
69
+ s.a = wrap(s.a - (50 / d) * s.speed * dt * k);
70
+ s.b = wrap(s.b + (50 / (d * 1.22)) * s.speed * dt * k);
71
+ if (front.current) front.current.style.transform = `translate3d(${s.a}%, 0, 0)`;
72
+ if (back.current) back.current.style.transform = `translate3d(${s.b}%, 0, 0)`;
73
+ if (visible) raf = requestAnimationFrame(frame);
74
+ else last = 0;
75
+ };
76
+ const start = () => {
77
+ if (!raf && visible) raf = requestAnimationFrame(frame);
78
+ };
79
+ const onScroll = () => {
80
+ if (!live.current.reactToScroll) return;
81
+ const now = performance.now();
82
+ const dy = window.scrollY - s.lastY;
83
+ const dtS = Math.max(0.016, (now - s.lastT) / 1000);
84
+ s.lastY = window.scrollY;
85
+ s.lastT = now;
86
+ if (!dy) return;
87
+ s.dir = Math.sign(dy);
88
+ s.speed = s.dir * (1 + Math.min(Math.abs(dy) / dtS / 350, 5));
89
+ };
90
+ const onEnter = () => (s.hover = true);
91
+ const onLeave = () => (s.hover = false);
92
+ window.addEventListener("scroll", onScroll, { passive: true });
93
+ el.addEventListener("pointerenter", onEnter);
94
+ el.addEventListener("pointerleave", onLeave);
95
+ const io = new IntersectionObserver(([entry]) => {
96
+ visible = entry.isIntersecting;
97
+ if (visible) start();
98
+ });
99
+ io.observe(el);
100
+ start();
101
+ return () => {
102
+ cancelAnimationFrame(raf);
103
+ io.disconnect();
104
+ window.removeEventListener("scroll", onScroll);
105
+ el.removeEventListener("pointerenter", onEnter);
106
+ el.removeEventListener("pointerleave", onLeave);
107
+ };
108
+ }, []);
109
+
110
+ const band = "absolute left-[-5%] right-[-5%] overflow-hidden";
111
+ return (
112
+ <div ref={root} aria-label={`Tech stack: ${list.map((i) => i.label).join(", ")}`} className={`relative w-full overflow-hidden pb-24 pt-14 md:pb-32 md:pt-20 ${className}`}>
113
+ {/* Spacer so the wrapper has the height of the front band. */}
114
+ <div aria-hidden="true" className="invisible flex items-center py-4 md:py-5">
115
+ <span className="block h-11 md:h-14" />
116
+ </div>
117
+ {showBackBand && (
118
+ <div aria-hidden="true" className={band} style={{ top: "60%", transform: `translateY(-50%) rotate(${tilt}deg)`, background: backColor, borderBlock: `2px solid ${bandInk}` }}>
119
+ <div ref={back} className="flex w-max will-change-transform" style={{ transform: "translate3d(-25%, 0, 0)" }}>
120
+ {[0, 1].map((copy) => (
121
+ <div key={copy} className="flex shrink-0 items-center py-3 md:py-4">
122
+ {row.map((it, i) => (
123
+ <span key={i} className="flex items-center">
124
+ <span className="whitespace-nowrap text-lg font-bold uppercase tracking-tight md:text-2xl" style={{ color: "rgba(255,255,255,.8)" }}>
125
+ {it.label}
126
+ </span>
127
+ <span className="mx-5 block h-2.5 w-2.5 rotate-45 md:mx-8 md:h-3 md:w-3" style={{ background: accentColor }} />
128
+ </span>
129
+ ))}
130
+ </div>
131
+ ))}
132
+ </div>
133
+ </div>
134
+ )}
135
+ <div aria-hidden="true" className={band} style={{ top: "50%", transform: `translateY(-50%) rotate(${-tilt}deg)`, background: accentColor, borderBlock: `2px solid ${bandInk}`, boxShadow: "0 10px 0 rgb(0 0 0 / .08)" }}>
136
+ <div ref={front} className="flex w-max will-change-transform">
137
+ {[0, 1].map((copy) => (
138
+ <div key={copy} className="flex shrink-0 items-center py-4 md:py-5">
139
+ {row.map((it, i) => (
140
+ <span key={i} className="flex items-center">
141
+ <span className="mr-3 grid h-11 w-11 shrink-0 place-items-center rounded-xl md:mr-4 md:h-14 md:w-14" style={{ background: tileColor, border: `2px solid ${bandInk}`, boxShadow: `4px 4px 0 ${bandInk}`, color: bandInk }}>
142
+ <BrandIcon icon={it.icon} label={it.label} />
143
+ </span>
144
+ <span className="whitespace-nowrap font-extrabold uppercase leading-none tracking-tight" style={{ fontSize: "clamp(1.5rem, 3.4vw, 3rem)", color: bandInk }}>
145
+ {it.label}
146
+ </span>
147
+ <span className="mx-5 block h-2.5 w-2.5 rotate-45 md:mx-8 md:h-3 md:w-3" style={{ background: bandInk }} />
148
+ </span>
149
+ ))}
150
+ </div>
151
+ ))}
152
+ </div>
153
+ </div>
154
+ </div>
155
+ );
156
+ };
157
+
158
+ export default CrossedMarquee;
@@ -0,0 +1,207 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { Color, Mesh, OrthographicCamera, PlaneGeometry, Scene, ShaderMaterial, Vector2, Vector3, WebGLRenderer } from "three";
3
+
4
+ /*
5
+ * Dither Waves: slow flowing waves turned into crisp two-tone pixel art with
6
+ * ordered (Bayer) dithering, drawn in one WebGL shader. The pointer drops
7
+ * ripples that spread through the pattern. Pick the two colors, the pixel
8
+ * size and the matrix (4x4 is chunkier, 8x8 smoother).
9
+ */
10
+
11
+ const VERT = `void main(){ gl_Position = vec4(position.xy, 0.0, 1.0); }`;
12
+
13
+ const FRAG = `
14
+ precision highp float;
15
+ uniform vec2 uRes; uniform float uTime; uniform float uPixel; uniform float uScale; uniform float uContrast;
16
+ uniform vec3 uInk; uniform vec3 uPaper; uniform float uMatrix;
17
+ uniform vec3 uRipples[6]; // x, y in pixels, start time
18
+ uniform float uRipple; uniform vec2 uMouse; uniform float uHover;
19
+
20
+ float bayer2(vec2 a){ a = floor(a); return fract(a.x / 2.0 + a.y * a.y * 0.75); }
21
+ float bayer4(vec2 a){ return bayer2(0.5 * a) * 0.25 + bayer2(a); }
22
+ float bayer8(vec2 a){ return bayer4(0.5 * a) * 0.25 + bayer2(a); }
23
+
24
+ float h2(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
25
+ float n2(vec2 p){
26
+ vec2 i = floor(p); vec2 f = fract(p); vec2 u = f * f * (3.0 - 2.0 * f);
27
+ return mix(mix(h2(i), h2(i + vec2(1, 0)), u.x), mix(h2(i + vec2(0, 1)), h2(i + vec2(1, 1)), u.x), u.y);
28
+ }
29
+ float fbm(vec2 p){ float v = 0.0; float a = 0.5; for (int i = 0; i < 5; i++) { v += a * n2(p); p = p * 2.03 + 11.7; a *= 0.5; } return v; }
30
+
31
+ void main(){
32
+ // Snap to the pixel grid first so the whole field reads as chunky pixels.
33
+ vec2 cell = floor(gl_FragCoord.xy / uPixel);
34
+ vec2 px = (cell + 0.5) * uPixel;
35
+ vec2 p = px / uRes.y * uScale;
36
+ float t = uTime;
37
+
38
+ // Domain-warped waves drifting diagonally.
39
+ vec2 w = vec2(fbm(p * 0.9 + vec2(t * 0.06, -t * 0.04)), fbm(p * 0.9 + vec2(-t * 0.05, t * 0.07) + 5.2));
40
+ float v = fbm(p + w * 1.6 + vec2(t * 0.03, 0.0));
41
+ v += 0.18 * sin(p.x * 1.3 + p.y * 0.7 - t * 0.6 + w.x * 4.0);
42
+
43
+ // Ripples from the pointer.
44
+ float rip = 0.0;
45
+ for (int i = 0; i < 6; i++) {
46
+ vec3 r = uRipples[i];
47
+ float age = t - r.z;
48
+ if (r.z > 0.0 && age < 3.5) {
49
+ float dist = length(px - r.xy) / uRes.y;
50
+ float front = age * 0.45;
51
+ rip += sin((dist - front) * 38.0) * exp(-abs(dist - front) * 14.0) * exp(-age * 0.9);
52
+ }
53
+ }
54
+ v += rip * 0.22 * uRipple;
55
+ // A soft glow under the pointer.
56
+ v += uHover * 0.18 * exp(-length(px - uMouse) / uRes.y * 7.0);
57
+
58
+ v = clamp((v - 0.5) * uContrast + 0.5, 0.0, 1.0);
59
+ float threshold = uMatrix > 6.0 ? bayer8(cell) : bayer4(cell);
60
+ float on = step(threshold, v);
61
+ gl_FragColor = vec4(mix(uPaper, uInk, on), 1.0);
62
+ }`;
63
+
64
+ const DitherWaves = ({ ink = "#c8f560", paper = "#0b0d0a", pixelSize = 3, matrix = 8, scale = 2.4, contrast = 1.35, speed = 1, ripple = 1, interactive = true, className = "", children }) => {
65
+ const wrap = useRef(null);
66
+ const live = useRef({});
67
+ live.current = { ink, paper, pixelSize, matrix, scale, contrast, speed, ripple, interactive };
68
+
69
+ useEffect(() => {
70
+ const el = wrap.current;
71
+ const renderer = new WebGLRenderer({ antialias: false, alpha: false, powerPreference: "high-performance" });
72
+ const dpr = Math.min(window.devicePixelRatio || 1, 2);
73
+ renderer.setPixelRatio(dpr);
74
+ Object.assign(renderer.domElement.style, { position: "absolute", inset: "0", width: "100%", height: "100%", display: "block", imageRendering: "pixelated" });
75
+ el.prepend(renderer.domElement);
76
+ const scene = new Scene();
77
+ const camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
78
+ const u = {
79
+ uRes: { value: new Vector2(1, 1) },
80
+ uTime: { value: 0 },
81
+ uPixel: { value: 3 },
82
+ uScale: { value: 2.4 },
83
+ uContrast: { value: 1.35 },
84
+ uInk: { value: new Color() },
85
+ uPaper: { value: new Color() },
86
+ uMatrix: { value: 8 },
87
+ uRipples: { value: Array.from({ length: 6 }, () => new Vector3()) },
88
+ uRipple: { value: 1 },
89
+ uMouse: { value: new Vector2(-9999, -9999) },
90
+ uHover: { value: 0 },
91
+ };
92
+ const material = new ShaderMaterial({ vertexShader: VERT, fragmentShader: FRAG, uniforms: u, depthTest: false, depthWrite: false });
93
+ const geometry = new PlaneGeometry(2, 2);
94
+ scene.add(new Mesh(geometry, material));
95
+
96
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
97
+ const mouse = { x: -9999, y: -9999, hover: 0, in: false, lastDrop: 0, lx: 0, ly: 0 };
98
+ let slot = 0;
99
+ let raf = 0;
100
+ let last = 0;
101
+ let time = 1;
102
+ let visible = true;
103
+ let h = 1;
104
+
105
+ const fit = () => {
106
+ const { width, height } = el.getBoundingClientRect();
107
+ if (!width || !height) return;
108
+ h = height;
109
+ renderer.setSize(width, height, false);
110
+ u.uRes.value.set(width * dpr, height * dpr);
111
+ if (!raf) draw(performance.now());
112
+ };
113
+ const drop = (x, y) => {
114
+ const r = u.uRipples.value[slot];
115
+ r.set(x * dpr, (h - y) * dpr, time);
116
+ slot = (slot + 1) % 6;
117
+ };
118
+ const draw = (now) => {
119
+ const o = live.current;
120
+ const dt = last ? Math.min(0.05, (now - last) / 1000) : 1 / 60;
121
+ last = now;
122
+ time += dt * o.speed;
123
+ mouse.hover += ((o.interactive && mouse.in ? 1 : 0) - mouse.hover) * (1 - Math.exp(-dt / 0.25));
124
+ u.uTime.value = time;
125
+ u.uPixel.value = Math.max(1, o.pixelSize) * dpr;
126
+ u.uScale.value = o.scale;
127
+ u.uContrast.value = o.contrast;
128
+ // The shader writes these straight to the screen, so keep them in sRGB.
129
+ u.uInk.value.set(o.ink).convertLinearToSRGB();
130
+ u.uPaper.value.set(o.paper).convertLinearToSRGB();
131
+ u.uMatrix.value = o.matrix;
132
+ u.uRipple.value = o.interactive ? o.ripple : 0;
133
+ u.uMouse.value.set(mouse.x * dpr, (h - mouse.y) * dpr);
134
+ u.uHover.value = mouse.hover;
135
+ renderer.render(scene, camera);
136
+ };
137
+ const loop = (now) => {
138
+ raf = 0;
139
+ draw(now);
140
+ if (visible && !reduced) raf = requestAnimationFrame(loop);
141
+ };
142
+ const start = () => {
143
+ if (!raf && visible && !reduced) {
144
+ last = 0;
145
+ raf = requestAnimationFrame(loop);
146
+ }
147
+ };
148
+
149
+ const local = (e) => {
150
+ const r = el.getBoundingClientRect();
151
+ return [e.clientX - r.left, e.clientY - r.top];
152
+ };
153
+ const onMove = (e) => {
154
+ [mouse.x, mouse.y] = local(e);
155
+ mouse.in = true;
156
+ // Moving drops a ripple every so often, spaced by distance.
157
+ if (live.current.interactive && Math.hypot(mouse.x - mouse.lx, mouse.y - mouse.ly) > 90 && time - mouse.lastDrop > 0.25) {
158
+ drop(mouse.x, mouse.y);
159
+ mouse.lastDrop = time;
160
+ mouse.lx = mouse.x;
161
+ mouse.ly = mouse.y;
162
+ }
163
+ };
164
+ const onDown = (e) => {
165
+ if (!live.current.interactive) return;
166
+ const [x, y] = local(e);
167
+ drop(x, y);
168
+ };
169
+ const onLeave = () => {
170
+ mouse.in = false;
171
+ };
172
+ el.addEventListener("pointermove", onMove);
173
+ el.addEventListener("pointerdown", onDown);
174
+ el.addEventListener("pointerleave", onLeave);
175
+ const ro = new ResizeObserver(fit);
176
+ ro.observe(el);
177
+ const io = new IntersectionObserver(([entry]) => {
178
+ visible = entry.isIntersecting;
179
+ if (visible) start();
180
+ });
181
+ io.observe(el);
182
+ fit();
183
+ start();
184
+
185
+ return () => {
186
+ cancelAnimationFrame(raf);
187
+ ro.disconnect();
188
+ io.disconnect();
189
+ el.removeEventListener("pointermove", onMove);
190
+ el.removeEventListener("pointerdown", onDown);
191
+ el.removeEventListener("pointerleave", onLeave);
192
+ geometry.dispose();
193
+ material.dispose();
194
+ renderer.dispose();
195
+ renderer.forceContextLoss();
196
+ renderer.domElement.remove();
197
+ };
198
+ }, []);
199
+
200
+ return (
201
+ <div ref={wrap} className={`relative h-full w-full overflow-hidden ${className}`} style={{ background: paper }}>
202
+ {children && <div className="relative z-10 h-full w-full">{children}</div>}
203
+ </div>
204
+ );
205
+ };
206
+
207
+ export default DitherWaves;
@@ -92,8 +92,9 @@ const FlipClock = ({ mode = "seconds", target, seconds = 3 * 86400 + 4 * 3600 +
92
92
 
93
93
  return (
94
94
  // The clock is sized in em; on narrow screens the font shrinks with the
95
- // container (cqw) so every unit fits instead of being cut off.
96
- <div className="flex w-full justify-center" style={{ containerType: "inline-size" }}>
95
+ // container (cqw) so every unit fits instead of being cut off. The min
96
+ // width keeps it from collapsing inside a shrink-to-fit parent.
97
+ <div className="flex w-full justify-center" style={{ containerType: "inline-size", minWidth: `min(${(size * parts.length * 2.55).toFixed(2)}rem, 92vw)` }}>
97
98
  <div role="timer" aria-live="off" aria-label={parts.map(([l, v]) => `${v} ${l}`).join(", ")} className={`inline-flex items-start gap-[0.35em] font-mono font-bold ${className}`} style={{ fontSize: `min(${size}rem, ${(100 / (parts.length * 2.55)).toFixed(2)}cqw)` }}>
98
99
  {parts.map(([label, value], i) => (
99
100
  <React.Fragment key={label}>
@@ -6,24 +6,31 @@ function cn(...classes) {
6
6
  import React, { useRef } from "react";
7
7
  import { motion, useMotionValue, useSpring, useMotionTemplate } from "framer-motion";
8
8
  const bgHexa = 'https://ik.imagekit.io/ny6yinyut/apexui/assets/bg-hexa.jpg?tr=f-auto,q-80';
9
- export default function GlareCardDemo() {
10
- return (
11
- <GlareCard className="flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-slate-800 to-[#181825] p-8 border border-slate-800 shadow-2xl relative overflow-hidden group group-hover:scale-100 transition-transform duration-300 ease-in-out">
12
- {/* Background Image */}
13
- <img
14
- src={bgHexa}
15
- alt="bg"
16
- className="absolute inset-0 w-full h-full object-cover opacity-30 pointer-events-none select-none z-0"
17
- draggable="false"
18
- />
19
- <div className="flex items-center justify-center w-20 h-20 rounded-md bg-gradient-to-br from-[#23233a] to-[#181825] border-2 border-[#a855f7]/60 shadow-xl mb-4 z-10 group">
20
- <span className="text-5xl font-extrabold text-[#a855f7] drop-shadow-[0_2px_12px_#a855f7aa]">A</span>
21
- </div>
22
- <p className="text-slate-200 font-extrabold text-2xl mt-2 tracking-wide z-10">ApexUI</p>
23
- </GlareCard>
24
- );
25
- }
26
-
9
+ // Shown when no children are passed (the look from the docs preview).
10
+ const DEMO_CLASS = "flex flex-col items-center justify-center bg-gradient-to-br from-gray-900 via-slate-800 to-[#181825] p-8 border border-slate-800 shadow-2xl relative overflow-hidden group group-hover:scale-100 transition-transform duration-300 ease-in-out";
11
+ const DemoContent = () => (
12
+ <>
13
+ <img src={bgHexa} alt="bg" className="absolute inset-0 w-full h-full object-cover opacity-30 pointer-events-none select-none z-0" draggable="false" />
14
+ <div className="flex items-center justify-center w-20 h-20 rounded-md bg-gradient-to-br from-[#23233a] to-[#181825] border-2 border-[#a855f7]/60 shadow-xl mb-4 z-10 group">
15
+ <span className="text-5xl font-extrabold text-[#a855f7] drop-shadow-[0_2px_12px_#a855f7aa]">A</span>
16
+ </div>
17
+ <p className="text-slate-200 font-extrabold text-2xl mt-2 tracking-wide z-10">ApexUI</p>
18
+ </>
19
+ );
20
+
21
+ // Default export: renders your children inside the card, or the demo when there are none.
22
+ export default function GlareCardDefault({ children, className = "", ...rest }) {
23
+ return children ? (
24
+ <GlareCard className={className} {...rest}>
25
+ {children}
26
+ </GlareCard>
27
+ ) : (
28
+ <GlareCard className={`${DEMO_CLASS} ${className}`} {...rest}>
29
+ <DemoContent />
30
+ </GlareCard>
31
+ );
32
+ }
33
+
27
34
  export function GlareCard({
28
35
  children,
29
36
  className = "",