apex-ui-kit 2.1.2 → 2.2.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/dist/ApexUI-Kit/templates/AnimatedButton/AnimatedButton.jsx +251 -0
  2. package/dist/ApexUI-Kit/templates/AnimatedTabs/AnimatedTabs.jsx +99 -0
  3. package/dist/ApexUI-Kit/templates/BlurRevealText/BlurRevealText.jsx +60 -0
  4. package/dist/ApexUI-Kit/templates/Carousel3D/Carousel3D.jsx +222 -0
  5. package/dist/ApexUI-Kit/templates/DotGlobe/DotGlobe.jsx +289 -0
  6. package/dist/ApexUI-Kit/templates/DotWaveBackground/DotWaveBackground.jsx +189 -0
  7. package/dist/ApexUI-Kit/templates/DynamicIsland/DynamicIsland.jsx +221 -0
  8. package/dist/ApexUI-Kit/templates/FaqAccordion/FaqAccordion.jsx +88 -0
  9. package/dist/ApexUI-Kit/templates/FlipClock/FlipClock.jsx +121 -0
  10. package/dist/ApexUI-Kit/templates/LiquidGradient/LiquidGradient.jsx +238 -0
  11. package/dist/ApexUI-Kit/templates/LoadingScreen/LoadingScreen.jsx +361 -0
  12. package/dist/ApexUI-Kit/templates/MagneticCursor/MagneticCursor.jsx +306 -0
  13. package/dist/ApexUI-Kit/templates/MagnifyDock/MagnifyDock.jsx +154 -0
  14. package/dist/ApexUI-Kit/templates/ProfileCard/ProfileCard.jsx +213 -0
  15. package/dist/ApexUI-Kit/templates/RollingCounter/RollingCounter.jsx +143 -0
  16. package/dist/ApexUI-Kit/templates/ScrambleText/ScrambleText.jsx +132 -0
  17. package/dist/ApexUI-Kit/templates/SidebarNav/SidebarNav.jsx +220 -0
  18. package/dist/ApexUI-Kit/templates/SpotlightGrid/SpotlightGrid.jsx +110 -0
  19. package/dist/ApexUI-Kit/templates/ToastStack/ToastStack.jsx +108 -0
  20. package/dist/ApexUI-Kit/templates/ToolTip/ToolTip.jsx +5 -3
  21. package/dist/ApexUI-Kit/templates/WebGLCarousel/WebGLCarousel.jsx +326 -0
  22. package/dist/ApexUI-Kit/templates/WorldMap/WorldMap.jsx +359 -0
  23. package/dist/cli/index.js +25 -0
  24. package/package.json +1 -1
@@ -0,0 +1,359 @@
1
+ import React, { useEffect, useId, useMemo, useRef, useState } from "react";
2
+ import { AnimatePresence, motion, useReducedMotion } from "framer-motion";
3
+ import { MapPin, Minus, Plus, RotateCcw, X } from "lucide-react";
4
+
5
+ /*
6
+ * World Map: a flat dotted world (canvas dots, SVG overlay) in three modes.
7
+ * connections – flights arc between cities with a travelling glow
8
+ * live – random pings pop up across the land with an online counter
9
+ * locations – office pins with labels; click one for its info card
10
+ */
11
+
12
+ // 2° land mask (Natural Earth 110m): 180 × 69 cells from 180°W, 78°N, one bit each.
13
+ const LAND = "AAAAwADkw////wEABAAAAABgAAAAAAAAAADAUT8A/v8PAAAAAAwA/j8AuAEAAAAAcBHtDcD/fwAAAAAwAPz/fwMAABCAAQD5w/wD8P8FAAAcAIL7//9//xOAAP/ff0668QD/HwAA+A8At///////v3/w/////x8++B8AAOD/1//7////////jP////+/+AE/gAcAn9f//////////w/w/////wEs4AEAAHz+////////////gL////8HeAAcAADg5///////////9ADgAf7/f4AnAAAAAH78////////H0QAAAiA//8f8AcAAIBB4////////38ADwAQAOD//5//AQAAHAT/////////A3AAAAAA/v//+T8AAGDz//////////8DAQAAAMD/////AwAAsP//////////LwAAAAAA6P///2IAAAD+//////////8CAAAAAAD///8/CAAA4P//////////JwAAAAAA8P///wYAAAD+/unz/////z8AAAAAAAD///8HAAAA/pgPPP//////MQAAAAAA8P//PwAAAMBD9v7n/////wcBAAAAAAD///8AAAAAPkD7f/7///8hEAAAAAAA4P//DwAAAIDhAv/n////f8YAAAAAAAD8//8AAAAA+AdE//////8jDwAAAAAAgP//AwAAAMD/APD/////PxgAAAAAAADw/x8AAAAA/n/v//////8HAAAAAAAAAPwDAgAAAOD////7////fwAAAAAAAACgHyAAAACA//9/f/7///8DAAAAAAAAAPQBAAAAAPj//+cv+P//PwAAAAAAAAAAHjAAAADA/////g/+//8EAAAAAAAAAOBhCAAAAP7//99/4D//AAAAAAAAAAAAPAMEAADA////+Qf84BcAAAAAAAAAAAA/AAAAAPz//58fgAf+QAAAAAAAAAAAAA8AAADg////ewA4gA8EAAAAAAAAAADAAAAAAPz//38BgAP4QQAAAAAAAAAAAAgPAADA////zwAwgAwQAAAAAAAAAAAA9Q8AAPj///8HAAVIAAAAAAAAAAAAAID/AQAA////fwBAAAAQAAAAAAAAAAAA+P8AAGDh//8DAAA0GAAAAAAAAAAAAID/HwAAAPj/HwAAgMIBAAAAAAAAAAAA/P8BAACA//8AAAAYXgAAAAAAAAAAAMD/fwAAAPz/BwAAAOOBAQAAAAAAAAAA/P8/AACA/z8AAABgbtQBAAAAAAAAAOD//w8AAPD/AwAAAAQIeAAAAAAAAAAA/P//AQAA/z8AAACAA4APAQAAAAAAAID//w8AAPD/AwAAAAARsEAAAAAAAAAA+P9/AAAA/j8AAAAAAAAAAAAAAAAAAAD//wcAAPD/QwAAAACAIwAAAAAAAAAA8P9/AAAA/z8EAAAAAD8GIAAAAAAAAAD8/wMAAPD/cQAAAAD4ZwAAAAAAAAAAgP8/AAAA/w8HAAAAgP8HAAAAAAAAAAD4/wMAAOD/MAAAAAD//wEBAAAAAAAAgP8PAAAA/g8DAAAA+P8fAAAAAAAAAAD4PwAAAOB/EAAAAID//wMAAAAAAAAAgP8DAAAA/AMAAAAA+P9/AAAAAAAAAAD8HwAAAMA/AAAAAID//wcAAAAAAAAAwP8BAAAA+AEAAAAA8P9/AAAAAAAAAAD8DwAAAIAPAAAAAAAP/gMAAAAAAAAAwB8AAAAAAAAAAAAAEIAfAAEAAAAAAAD+AwAAAAAAAAAAAAAA8AEgAAAAAAAA4AcAAAAAAAAAAAAAAAAAAAYAAAAAAABeAAAAAAAAAAAAAAAAwAAwAAAAAAAAwAMAAAAAAAAAAAAAAAAIgAEAAAAAAAAeAAAAAAAAAAAAAAAAAAAMAAAAAAAA4AEAAAAAAAAAAAAAAAAAAAAAAAAAAAAPAAAAAAAAAAABAAAAAAAAAAAAAAAAcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
14
+ const STEP = 2;
15
+ const COLS = 180;
16
+ const ROWS = 69;
17
+
18
+ const landCells = (() => {
19
+ const bin = typeof atob === "function" ? atob(LAND) : "";
20
+ const cells = [];
21
+ for (let i = 0; i < COLS * ROWS; i++) if ((bin.charCodeAt(i >> 3) >> (i & 7)) & 1) cells.push([i % COLS, Math.floor(i / COLS)]);
22
+ return cells;
23
+ })();
24
+
25
+ // Lat/lon to map units (one unit per 2° cell).
26
+ const toXY = (lat, lon) => [(lon + 180) / STEP, (78 - lat) / STEP];
27
+
28
+ const CITIES = {
29
+ bbsr: { label: "Bhubaneswar", lat: 20.3, lon: 85.8, note: "HQ · 42 people" },
30
+ sf: { label: "San Francisco", lat: 37.8, lon: -122.4, note: "Design studio · 18 people" },
31
+ ldn: { label: "London", lat: 51.5, lon: -0.1, note: "Sales · 11 people" },
32
+ tyo: { label: "Tokyo", lat: 35.7, lon: 139.7, note: "Engineering · 23 people" },
33
+ syd: { label: "Sydney", lat: -33.9, lon: 151.2, note: "Support · 7 people" },
34
+ sp: { label: "São Paulo", lat: -23.5, lon: -46.6, note: "Partners · 5 people" },
35
+ nbo: { label: "Nairobi", lat: -1.3, lon: 36.8, note: "Research · 4 people" },
36
+ };
37
+
38
+ const DEFAULT_LOCATIONS = Object.values(CITIES);
39
+ const DEFAULT_CONNECTIONS = [
40
+ ["bbsr", "sf"],
41
+ ["bbsr", "ldn"],
42
+ ["bbsr", "tyo"],
43
+ ["bbsr", "syd"],
44
+ ["ldn", "sp"],
45
+ ["ldn", "nbo"],
46
+ ["tyo", "sf"],
47
+ ];
48
+
49
+ function DotsCanvas({ dotColor, zoom }) {
50
+ const ref = useRef(null);
51
+ useEffect(() => {
52
+ const canvas = ref.current;
53
+ const ctx = canvas.getContext("2d");
54
+ const draw = () => {
55
+ // Layout size (ignores the zoom transform), drawn at a higher resolution when zoomed in.
56
+ const width = canvas.offsetWidth;
57
+ const height = canvas.offsetHeight;
58
+ const dpr = Math.min((window.devicePixelRatio || 1) * zoom, 6);
59
+ canvas.width = Math.round(width * dpr);
60
+ canvas.height = Math.round(height * dpr);
61
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
62
+ const sx = width / COLS;
63
+ const sy = height / ROWS;
64
+ const r = Math.max(0.7, Math.min(sx, sy) * 0.28);
65
+ ctx.clearRect(0, 0, width, height);
66
+ ctx.fillStyle = dotColor;
67
+ for (const [c, row] of landCells) {
68
+ ctx.beginPath();
69
+ ctx.arc((c + 0.5) * sx, (row + 0.5) * sy, r, 0, Math.PI * 2);
70
+ ctx.fill();
71
+ }
72
+ };
73
+ const ro = new ResizeObserver(draw);
74
+ ro.observe(canvas);
75
+ draw();
76
+ return () => ro.disconnect();
77
+ }, [dotColor, zoom]);
78
+ return <canvas ref={ref} aria-hidden="true" className="absolute inset-0 h-full w-full" />;
79
+ }
80
+
81
+ const MAX_ZOOM = 6;
82
+
83
+ /* Zoom and pan state for the map: buttons, Ctrl/⌘ + wheel, double-click, drag and pinch. */
84
+ function useZoom(viewRef) {
85
+ const [view, setView] = useState({ k: 1, x: 0, y: 0 });
86
+ const pointers = useRef(new Map());
87
+ const gesture = useRef(null);
88
+ const [dragging, setDragging] = useState(false);
89
+
90
+ // Keep the zoomed map covering the frame.
91
+ const clamp = (v) => {
92
+ const el = viewRef.current;
93
+ const w = el?.offsetWidth ?? 0;
94
+ const h = el?.offsetHeight ?? 0;
95
+ const k = Math.min(MAX_ZOOM, Math.max(1, v.k));
96
+ return { k, x: Math.min(0, Math.max(w * (1 - k), v.x)), y: Math.min(0, Math.max(h * (1 - k), v.y)) };
97
+ };
98
+ // Zoom by a factor around a point (in frame pixels), keeping that point still.
99
+ const zoomAt = (factor, px, py) =>
100
+ setView((v) => {
101
+ const k = Math.min(MAX_ZOOM, Math.max(1, v.k * factor));
102
+ const r = k / v.k;
103
+ return clamp({ k, x: px - (px - v.x) * r, y: py - (py - v.y) * r });
104
+ });
105
+ const center = () => {
106
+ const el = viewRef.current;
107
+ return [el.offsetWidth / 2, el.offsetHeight / 2];
108
+ };
109
+ const local = (e) => {
110
+ const r = viewRef.current.getBoundingClientRect();
111
+ return [e.clientX - r.left, e.clientY - r.top];
112
+ };
113
+
114
+ useEffect(() => {
115
+ const el = viewRef.current;
116
+ const onWheel = (e) => {
117
+ if (!e.ctrlKey && !e.metaKey) return;
118
+ e.preventDefault();
119
+ const [px, py] = local(e);
120
+ zoomAt(Math.exp(-e.deltaY * 0.0025), px, py);
121
+ };
122
+ el.addEventListener("wheel", onWheel, { passive: false });
123
+ return () => el.removeEventListener("wheel", onWheel);
124
+ // eslint-disable-next-line react-hooks/exhaustive-deps
125
+ }, []);
126
+
127
+ const handlers = {
128
+ onDoubleClick: (e) => {
129
+ const [px, py] = local(e);
130
+ zoomAt(2, px, py);
131
+ },
132
+ onPointerDown: (e) => {
133
+ pointers.current.set(e.pointerId, local(e));
134
+ gesture.current = null;
135
+ setDragging(true);
136
+ },
137
+ onPointerMove: (e) => {
138
+ if (!pointers.current.has(e.pointerId)) return;
139
+ const prev = pointers.current.get(e.pointerId);
140
+ const now = local(e);
141
+ pointers.current.set(e.pointerId, now);
142
+ const pts = [...pointers.current.values()];
143
+ if (pts.length === 2) {
144
+ // Pinch: zoom by the change in finger distance, around their middle.
145
+ const d = Math.hypot(pts[0][0] - pts[1][0], pts[0][1] - pts[1][1]);
146
+ const mid = [(pts[0][0] + pts[1][0]) / 2, (pts[0][1] + pts[1][1]) / 2];
147
+ if (gesture.current?.d) zoomAt(d / gesture.current.d, mid[0], mid[1]);
148
+ gesture.current = { d };
149
+ } else if (pts.length === 1) {
150
+ setView((v) => (v.k > 1 ? clamp({ ...v, x: v.x + now[0] - prev[0], y: v.y + now[1] - prev[1] }) : v));
151
+ }
152
+ },
153
+ onPointerUp: (e) => {
154
+ pointers.current.delete(e.pointerId);
155
+ gesture.current = null;
156
+ setDragging(pointers.current.size > 0);
157
+ },
158
+ onPointerCancel: (e) => {
159
+ pointers.current.delete(e.pointerId);
160
+ setDragging(pointers.current.size > 0);
161
+ },
162
+ };
163
+
164
+ return {
165
+ view,
166
+ dragging,
167
+ handlers,
168
+ zoomIn: () => zoomAt(1.6, ...center()),
169
+ zoomOut: () => zoomAt(1 / 1.6, ...center()),
170
+ reset: () => setView({ k: 1, x: 0, y: 0 }),
171
+ };
172
+ }
173
+
174
+ const WorldMap = ({
175
+ variant = "connections",
176
+ locations = DEFAULT_LOCATIONS,
177
+ connections = DEFAULT_CONNECTIONS,
178
+ dotColor = "rgba(161,161,170,0.55)",
179
+ accentColor = "#a3e635",
180
+ pingRate = 700,
181
+ showLabels = true,
182
+ zoomable = true,
183
+ className = "",
184
+ }) => {
185
+ const gradId = `wm-${useId().replace(/[^a-zA-Z0-9-]/g, "")}`;
186
+ const viewRef = useRef(null);
187
+ const { view, dragging, handlers, zoomIn, zoomOut, reset } = useZoom(viewRef);
188
+ const k = zoomable ? view.k : 1;
189
+ const reduced = useReducedMotion();
190
+ const [pings, setPings] = useState([]);
191
+ const [online, setOnline] = useState(1284);
192
+ const [selected, setSelected] = useState(null);
193
+
194
+ const byKey = useMemo(() => {
195
+ const map = {};
196
+ Object.entries(CITIES).forEach(([k, v]) => (map[k] = v));
197
+ locations.forEach((l, i) => (map[l.key ?? i] = l));
198
+ return map;
199
+ }, [locations]);
200
+
201
+ // Live mode: drop a ping on a random land cell every few hundred ms.
202
+ useEffect(() => {
203
+ if (variant !== "live" || reduced) return undefined;
204
+ const id = setInterval(() => {
205
+ const [c, r] = landCells[Math.floor(Math.random() * landCells.length)];
206
+ setPings((list) => [...list.slice(-14), { id: performance.now(), x: c + 0.5, y: r + 0.5 }]);
207
+ setOnline((n) => Math.max(900, n + Math.round((Math.random() - 0.45) * 9)));
208
+ }, pingRate);
209
+ return () => clearInterval(id);
210
+ }, [variant, pingRate, reduced]);
211
+
212
+ const arcs = connections
213
+ .map(([a, b]) => [byKey[a], byKey[b]])
214
+ .filter(([a, b]) => a && b)
215
+ .map(([a, b]) => {
216
+ const [x1, y1] = toXY(a.lat, a.lon);
217
+ const [x2, y2] = toXY(b.lat, b.lon);
218
+ const dist = Math.hypot(x2 - x1, y2 - y1);
219
+ const cx = (x1 + x2) / 2;
220
+ const cy = Math.min(y1, y2) - dist * 0.32;
221
+ return { d: `M${x1} ${y1} Q${cx} ${cy} ${x2} ${y2}`, len: dist * 1.25 };
222
+ });
223
+
224
+ const pins = variant === "connections" ? Object.values(byKey).filter((l) => connections.some(([a, b]) => byKey[a] === l || byKey[b] === l)) : variant === "locations" ? locations : [];
225
+
226
+ return (
227
+ <div className={`relative w-full ${className}`}>
228
+ <div
229
+ ref={viewRef}
230
+ {...(zoomable ? handlers : {})}
231
+ className={`relative w-full overflow-hidden ${zoomable && k > 1 ? "cursor-grab touch-none active:cursor-grabbing" : "touch-pan-y"}`}
232
+ style={{ aspectRatio: `${COLS} / ${ROWS}` }}
233
+ >
234
+ <div
235
+ className={`absolute inset-0 origin-top-left ${dragging ? "" : "transition-transform duration-200 ease-out"}`}
236
+ style={{ transform: zoomable ? `translate(${view.x}px, ${view.y}px) scale(${k})` : undefined }}
237
+ >
238
+ <DotsCanvas dotColor={dotColor} zoom={Math.ceil(k)} />
239
+ <svg viewBox={`0 0 ${COLS} ${ROWS}`} preserveAspectRatio="none" className="absolute inset-0 h-full w-full overflow-visible">
240
+ <defs>
241
+ <linearGradient id={gradId} x1="0" x2="1">
242
+ <stop offset="0" stopColor={accentColor} stopOpacity="0" />
243
+ <stop offset="0.5" stopColor={accentColor} stopOpacity="1" />
244
+ <stop offset="1" stopColor={accentColor} stopOpacity="0" />
245
+ </linearGradient>
246
+ </defs>
247
+
248
+ {variant === "connections" &&
249
+ arcs.map((a, i) => (
250
+ <g key={i}>
251
+ <path d={a.d} fill="none" stroke={accentColor} strokeOpacity="0.3" strokeWidth={0.3 / k} />
252
+ <motion.path
253
+ d={a.d}
254
+ fill="none"
255
+ stroke={accentColor}
256
+ strokeLinecap="round"
257
+ strokeWidth={0.65 / k}
258
+ strokeDasharray={`${a.len * 0.18} ${a.len}`}
259
+ initial={{ strokeDashoffset: a.len * 0.18 }}
260
+ animate={reduced ? undefined : { strokeDashoffset: -a.len }}
261
+ transition={{ duration: 2.4, delay: i * 0.35, repeat: Infinity, repeatDelay: 0.6, ease: "easeInOut" }}
262
+ />
263
+ </g>
264
+ ))}
265
+
266
+ {variant === "live" && (
267
+ <AnimatePresence>
268
+ {pings.map((p) => (
269
+ <g key={p.id}>
270
+ <motion.circle cx={p.x} cy={p.y} fill={accentColor} initial={{ r: 0, opacity: 1 }} animate={{ r: 0.9, opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }} />
271
+ <motion.circle cx={p.x} cy={p.y} fill="none" stroke={accentColor} strokeWidth="0.25" initial={{ r: 0.5, opacity: 0.9 }} animate={{ r: 4, opacity: 0 }} transition={{ duration: 1.6, ease: "easeOut" }} />
272
+ </g>
273
+ ))}
274
+ </AnimatePresence>
275
+ )}
276
+ </svg>
277
+
278
+ {/* Pins are HTML so they stay round at any aspect ratio. */}
279
+ {pins.map((l, i) => {
280
+ const [x, y] = toXY(l.lat, l.lon);
281
+ const active = selected === l;
282
+ return (
283
+ <button
284
+ key={l.label ?? i}
285
+ type="button"
286
+ onClick={() => variant === "locations" && setSelected(active ? null : l)}
287
+ aria-label={l.label}
288
+ className={`group absolute ${variant === "locations" ? "cursor-pointer" : "cursor-default"}`}
289
+ // Counter-scale so pins keep their size while the map zooms.
290
+ style={{ left: `${(x / COLS) * 100}%`, top: `${(y / ROWS) * 100}%`, transform: `translate(-50%, -50%) scale(${1 / k})` }}
291
+ >
292
+ <span className="absolute left-1/2 top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 animate-ping rounded-full opacity-40 sm:h-5 sm:w-5" style={{ background: accentColor, animationDuration: "2.2s", animationDelay: `${i * 0.3}s` }} />
293
+ <span className="relative block h-1.5 w-1.5 rounded-full ring-2 ring-black/40 sm:h-2.5 sm:w-2.5" style={{ background: accentColor }} />
294
+ {showLabels && (
295
+ <span className={`pointer-events-none absolute left-3 top-1/2 hidden -translate-y-1/2 whitespace-nowrap rounded-md bg-black/75 px-1.5 py-0.5 text-[0.65rem] font-medium text-white backdrop-blur sm:block ${variant === "connections" ? "opacity-0 transition-opacity group-hover:opacity-100" : ""}`}>
296
+ {l.label}
297
+ </span>
298
+ )}
299
+ </button>
300
+ );
301
+ })}
302
+ </div>
303
+
304
+ {zoomable && (
305
+ <div className="absolute left-2 top-2 z-10 flex flex-col overflow-hidden rounded-lg border border-white/10 bg-neutral-950/80 text-white backdrop-blur">
306
+ <button type="button" aria-label="Zoom in" onClick={zoomIn} className="grid h-7 w-7 place-items-center hover:bg-white/10">
307
+ <Plus className="h-3.5 w-3.5" />
308
+ </button>
309
+ <button type="button" aria-label="Zoom out" onClick={zoomOut} disabled={k <= 1} className="grid h-7 w-7 place-items-center border-t border-white/10 hover:bg-white/10 disabled:opacity-35">
310
+ <Minus className="h-3.5 w-3.5" />
311
+ </button>
312
+ {k > 1 && (
313
+ <button type="button" aria-label="Reset zoom" onClick={reset} className="grid h-7 w-7 place-items-center border-t border-white/10 hover:bg-white/10">
314
+ <RotateCcw className="h-3.5 w-3.5" />
315
+ </button>
316
+ )}
317
+ </div>
318
+ )}
319
+
320
+ <AnimatePresence>
321
+ {variant === "locations" && selected && (
322
+ <motion.div
323
+ key={selected.label}
324
+ initial={{ opacity: 0, y: 8, scale: 0.96 }}
325
+ animate={{ opacity: 1, y: 0, scale: 1 }}
326
+ exit={{ opacity: 0, y: 6, scale: 0.97 }}
327
+ transition={{ duration: 0.2 }}
328
+ className="absolute bottom-3 left-3 flex max-w-[16rem] items-start gap-3 rounded-xl border border-white/10 bg-neutral-950/90 p-3 text-white shadow-xl backdrop-blur"
329
+ >
330
+ <span className="grid h-8 w-8 shrink-0 place-items-center rounded-lg text-black" style={{ background: accentColor }}>
331
+ <MapPin className="h-4 w-4" />
332
+ </span>
333
+ <div className="min-w-0">
334
+ <p className="text-[0.85rem] font-semibold">{selected.label}</p>
335
+ {selected.note && <p className="text-[0.75rem] text-white/60">{selected.note}</p>}
336
+ <p className="mt-1 font-mono text-[0.65rem] text-white/40">
337
+ {selected.lat.toFixed(1)}°, {selected.lon.toFixed(1)}°
338
+ </p>
339
+ </div>
340
+ <button type="button" aria-label="Close" onClick={() => setSelected(null)} className="ml-1 text-white/50 hover:text-white">
341
+ <X className="h-4 w-4" />
342
+ </button>
343
+ </motion.div>
344
+ )}
345
+ </AnimatePresence>
346
+ </div>
347
+
348
+ {variant === "live" && (
349
+ <div className="absolute right-3 top-3 flex items-center gap-2 rounded-full border border-white/10 bg-neutral-950/80 px-3 py-1.5 text-[0.75rem] text-white backdrop-blur">
350
+ <span className="h-2 w-2 animate-pulse rounded-full" style={{ background: accentColor }} />
351
+ <span className="font-semibold tabular-nums">{online.toLocaleString()}</span>
352
+ <span className="text-white/55">online now</span>
353
+ </div>
354
+ )}
355
+ </div>
356
+ );
357
+ };
358
+
359
+ export default WorldMap;
package/dist/cli/index.js CHANGED
@@ -48,6 +48,29 @@ const componentMap = {
48
48
  "testimonials": "Testimonials",
49
49
  "mask-view": "MaskView",
50
50
  "receipt-printer": "ReceiptPrinter",
51
+ "dynamic-island": "DynamicIsland",
52
+ "magnify-dock": "MagnifyDock",
53
+ "dot-wave-background": "DotWaveBackground",
54
+ "rolling-counter": "RollingCounter",
55
+ "scramble-text": "ScrambleText",
56
+ "3d-carousel": "Carousel3D",
57
+ "liquid-gradient": "LiquidGradient",
58
+ "webgl-carousel": "WebGLCarousel",
59
+ "magnetic-cursor": "MagneticCursor",
60
+ "spotlight-grid": "SpotlightGrid",
61
+ "sidebar-nav": "SidebarNav",
62
+ "animated-button": "AnimatedButton",
63
+ "animated-buttons": "AnimatedButton",
64
+ "profile-card": "ProfileCard",
65
+ "dot-globe": "DotGlobe",
66
+ "world-map": "WorldMap",
67
+ "loading-screen": "LoadingScreen",
68
+ "loading-screens": "LoadingScreen",
69
+ "animated-tabs": "AnimatedTabs",
70
+ "faq-accordion": "FaqAccordion",
71
+ "toast-stack": "ToastStack",
72
+ "blur-reveal-text": "BlurRevealText",
73
+ "flip-clock": "FlipClock",
51
74
  "water-drop-reveal": "MaskView",
52
75
  };
53
76
  // Packages only some components need, installed on top of the shared ones.
@@ -55,6 +78,8 @@ const extraDeps = {
55
78
  Cards: ["react-router-dom"],
56
79
  LuminousParticleOcean: ["@react-three/fiber"],
57
80
  SmokeBackground: ["ogl"],
81
+ LiquidGradient: ["ogl"],
82
+ WebGLCarousel: ["ogl"],
58
83
  };
59
84
  async function main() {
60
85
  const args = process.argv.slice(2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apex-ui-kit",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Apex UI Kit - A React component library for building modern user interfaces",
6
6
  "keywords": [