@allshift/ui 0.2.0 → 0.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/index.js CHANGED
@@ -203,9 +203,603 @@ function ScrollGenerateCard({
203
203
  phase === "idle" && /* @__PURE__ */ jsx4("div", { style: { visibility: "hidden" }, children: children("idle") })
204
204
  ] });
205
205
  }
206
+
207
+ // src/canvas/LiveCard.tsx
208
+ import {
209
+ motion as motion5,
210
+ useMotionValue as useMotionValue3,
211
+ useTransform as useTransform2,
212
+ useSpring as useSpring3,
213
+ useMotionTemplate as useMotionTemplate2
214
+ } from "framer-motion";
215
+
216
+ // src/canvas/useGenerate.ts
217
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
218
+ import { useInView as useInView2 } from "framer-motion";
219
+ function useGenerate(opts = {}) {
220
+ const {
221
+ enterDelay = 0,
222
+ skeletonMs = 420,
223
+ trigger = "view",
224
+ margin = "-60px 0px",
225
+ repeat = false
226
+ } = opts;
227
+ const ref = useRef3(null);
228
+ const inView = useInView2(ref, { once: !repeat, margin });
229
+ const [phase, setPhase] = useState2("idle");
230
+ useEffect2(() => {
231
+ const shouldRun = trigger === "mount" || inView;
232
+ if (!shouldRun) return;
233
+ if (!repeat && phase !== "idle") return;
234
+ let live;
235
+ const enter = setTimeout(() => {
236
+ setPhase("skeleton");
237
+ live = setTimeout(() => setPhase("live"), skeletonMs);
238
+ }, enterDelay);
239
+ return () => {
240
+ clearTimeout(enter);
241
+ clearTimeout(live);
242
+ };
243
+ }, [inView, trigger]);
244
+ return { ref, phase, isLive: phase === "live" };
245
+ }
246
+
247
+ // src/canvas/accents.ts
248
+ var ACCENT_RGB = {
249
+ teal: "0,229,240",
250
+ blue: "59,139,255",
251
+ purple: "168,124,255",
252
+ magenta: "232,48,248",
253
+ green: "0,224,128",
254
+ orange: "255,110,48",
255
+ neutral: "255,255,255"
256
+ };
257
+
258
+ // src/canvas/LiveCard.tsx
259
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
260
+ function resolveRgb(accent) {
261
+ if (accent === "brand") return { rgb: "var(--shift-accent-rgb, 91,140,255)", isBrand: true };
262
+ return { rgb: ACCENT_RGB[accent], isBrand: false };
263
+ }
264
+ var SKEL_WIDTHS = [72, 88, 60, 80, 66, 78, 54, 84];
265
+ function Skeleton({ rows, minHeight }) {
266
+ return /* @__PURE__ */ jsxs3(
267
+ motion5.div,
268
+ {
269
+ initial: { opacity: 0, y: 16 },
270
+ animate: { opacity: 1, y: 0 },
271
+ transition: { duration: 0.3 },
272
+ className: "ash-surface relative rounded-2xl border border-white/[0.07] p-5 overflow-hidden",
273
+ style: { minHeight },
274
+ children: [
275
+ /* @__PURE__ */ jsx5("div", { className: "h-5 w-28 rounded-full bg-white/[0.07] animate-pulse mb-5" }),
276
+ Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 mb-3", children: [
277
+ /* @__PURE__ */ jsx5("div", { className: "w-7 h-7 rounded-xl bg-white/5 animate-pulse shrink-0" }),
278
+ /* @__PURE__ */ jsx5(
279
+ "div",
280
+ {
281
+ className: "h-2.5 rounded-full bg-white/5 animate-pulse",
282
+ style: { width: `${SKEL_WIDTHS[i % SKEL_WIDTHS.length]}%` }
283
+ }
284
+ )
285
+ ] }, i)),
286
+ /* @__PURE__ */ jsx5(
287
+ motion5.div,
288
+ {
289
+ className: "absolute inset-0 pointer-events-none",
290
+ style: { background: "linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.05) 50%, transparent 100%)" },
291
+ animate: { x: ["-100%", "200%"] },
292
+ transition: { duration: 1.25, ease: "easeInOut", repeat: Infinity }
293
+ }
294
+ )
295
+ ]
296
+ }
297
+ );
298
+ }
299
+ function LiveCard({
300
+ children,
301
+ accent = "neutral",
302
+ tilt = true,
303
+ shine = true,
304
+ generate = true,
305
+ index = 0,
306
+ trigger = "view",
307
+ skeletonRows = 4,
308
+ topLine = true,
309
+ className = "",
310
+ minHeight = 180,
311
+ onClick
312
+ }) {
313
+ const { rgb } = resolveRgb(accent);
314
+ const { ref, phase } = useGenerate({
315
+ enterDelay: generate ? index * 75 : 0,
316
+ trigger
317
+ });
318
+ const effectivePhase = generate ? phase : "live";
319
+ const mx = useMotionValue3(0.5);
320
+ const my = useMotionValue3(0.5);
321
+ const rawRY = useTransform2(mx, [0, 1], [-7, 7]);
322
+ const rawRX = useTransform2(my, [0, 1], [4, -4]);
323
+ const rotateY = useSpring3(rawRY, { stiffness: 300, damping: 28 });
324
+ const rotateX = useSpring3(rawRX, { stiffness: 300, damping: 28 });
325
+ const shineX = useMotionValue3(50);
326
+ const shineY = useMotionValue3(50);
327
+ const shineBg = useMotionTemplate2`radial-gradient(circle at ${shineX}% ${shineY}%, rgba(${rgb},0.10) 0%, rgba(${rgb},0.035) 42%, transparent 66%)`;
328
+ function onMouseMove(e) {
329
+ if (!tilt && !shine) return;
330
+ const rect = e.currentTarget.getBoundingClientRect();
331
+ const nx = (e.clientX - rect.left) / rect.width;
332
+ const ny = (e.clientY - rect.top) / rect.height;
333
+ mx.set(nx);
334
+ my.set(ny);
335
+ shineX.set(nx * 100);
336
+ shineY.set(ny * 100);
337
+ }
338
+ function onMouseLeave() {
339
+ mx.set(0.5);
340
+ my.set(0.5);
341
+ shineX.set(50);
342
+ shineY.set(50);
343
+ }
344
+ if (effectivePhase === "idle") {
345
+ return /* @__PURE__ */ jsx5("div", { ref, className: `rounded-2xl border border-white/[0.04] ${className}`, style: { minHeight } });
346
+ }
347
+ if (effectivePhase === "skeleton") {
348
+ return /* @__PURE__ */ jsx5("div", { ref, className, children: /* @__PURE__ */ jsx5(Skeleton, { rows: skeletonRows, minHeight }) });
349
+ }
350
+ return /* @__PURE__ */ jsx5(
351
+ "div",
352
+ {
353
+ ref,
354
+ style: { perspective: tilt ? "1000px" : void 0 },
355
+ onMouseMove,
356
+ onMouseLeave,
357
+ className,
358
+ children: /* @__PURE__ */ jsxs3(
359
+ motion5.div,
360
+ {
361
+ style: {
362
+ rotateX: tilt ? rotateX : void 0,
363
+ rotateY: tilt ? rotateY : void 0,
364
+ background: shine ? shineBg : void 0,
365
+ transformStyle: tilt ? "preserve-3d" : void 0
366
+ },
367
+ initial: generate ? { opacity: 0, y: 12 } : false,
368
+ animate: { opacity: 1, y: 0 },
369
+ transition: { duration: 0.3 },
370
+ whileHover: tilt ? { scale: 1.012, y: -3 } : void 0,
371
+ onClick,
372
+ className: `ash-surface group relative rounded-2xl border border-white/[0.08] p-5 overflow-hidden transition-colors duration-300 ${onClick ? "cursor-pointer" : ""}`,
373
+ children: [
374
+ topLine && /* @__PURE__ */ jsx5(
375
+ motion5.div,
376
+ {
377
+ className: "absolute top-0 left-0 right-0 h-px",
378
+ style: { background: `linear-gradient(90deg, transparent, rgba(${rgb},0.9), transparent)`, transformOrigin: "left" },
379
+ initial: { scaleX: 0, opacity: 0 },
380
+ animate: { scaleX: 1, opacity: 0.7 },
381
+ transition: { duration: 0.5, ease: "easeOut" }
382
+ }
383
+ ),
384
+ children,
385
+ /* @__PURE__ */ jsx5(
386
+ "div",
387
+ {
388
+ className: "absolute bottom-0 right-0 w-32 h-32 rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none",
389
+ style: { background: `rgba(${rgb},0.18)` }
390
+ }
391
+ )
392
+ ]
393
+ }
394
+ )
395
+ }
396
+ );
397
+ }
398
+
399
+ // src/canvas/GenerativeView.tsx
400
+ import { Children, isValidElement } from "react";
401
+ import { motion as motion6 } from "framer-motion";
402
+ import { jsx as jsx6 } from "react/jsx-runtime";
403
+ function GenerativeView({
404
+ children,
405
+ stagger = 0.08,
406
+ delay = 0,
407
+ className = ""
408
+ }) {
409
+ const items = Children.toArray(children);
410
+ return /* @__PURE__ */ jsx6(
411
+ motion6.div,
412
+ {
413
+ className,
414
+ initial: "hidden",
415
+ animate: "show",
416
+ variants: {
417
+ hidden: {},
418
+ show: { transition: { staggerChildren: stagger, delayChildren: delay } }
419
+ },
420
+ children: items.map(
421
+ (child, i) => isValidElement(child) ? /* @__PURE__ */ jsx6(
422
+ motion6.div,
423
+ {
424
+ variants: {
425
+ hidden: { opacity: 0, y: 16, filter: "blur(6px)" },
426
+ show: {
427
+ opacity: 1,
428
+ y: 0,
429
+ filter: "blur(0px)",
430
+ transition: { type: "spring", stiffness: 260, damping: 28 }
431
+ }
432
+ },
433
+ children: child
434
+ },
435
+ child.key ?? i
436
+ ) : child
437
+ )
438
+ }
439
+ );
440
+ }
441
+
442
+ // src/canvas/LiveStat.tsx
443
+ import { useEffect as useEffect3, useRef as useRef4, useState as useState3 } from "react";
444
+ import { motion as motion7 } from "framer-motion";
445
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
446
+ function rgbFor(accent) {
447
+ if (accent === "brand") return "var(--shift-accent-rgb, 91,140,255)";
448
+ return ACCENT_RGB[accent];
449
+ }
450
+ function formatCompact(n, prefix, suffix) {
451
+ let body;
452
+ if (Math.abs(n) >= 1e6) body = `${(n / 1e6).toFixed(1)}M`;
453
+ else if (Math.abs(n) >= 1e3) body = `${(n / 1e3).toFixed(0)}K`;
454
+ else body = n.toLocaleString();
455
+ return `${prefix}${body}${suffix}`;
456
+ }
457
+ function LiveStat({
458
+ label,
459
+ value,
460
+ display,
461
+ prefix = "",
462
+ suffix = "",
463
+ sub,
464
+ icon: Icon,
465
+ accent = "neutral",
466
+ duration = 1200,
467
+ index = 0
468
+ }) {
469
+ const rgb = rgbFor(accent);
470
+ const [n, setN] = useState3(0);
471
+ const raf = useRef4(0);
472
+ useEffect3(() => {
473
+ if (value == null || display != null) return;
474
+ const start = performance.now();
475
+ const step = (now) => {
476
+ const pct = Math.min((now - start) / duration, 1);
477
+ const eased = 1 - Math.pow(1 - pct, 3);
478
+ setN(value * eased);
479
+ if (pct < 1) raf.current = requestAnimationFrame(step);
480
+ };
481
+ raf.current = requestAnimationFrame(step);
482
+ return () => cancelAnimationFrame(raf.current);
483
+ }, [value, display, duration]);
484
+ const shown = display ?? (value != null ? formatCompact(n, prefix, suffix) : "\u2014");
485
+ return /* @__PURE__ */ jsxs4(
486
+ motion7.div,
487
+ {
488
+ initial: { opacity: 0, y: 16, scale: 0.96 },
489
+ animate: { opacity: 1, y: 0, scale: 1 },
490
+ transition: { type: "spring", stiffness: 280, damping: 26, delay: index * 0.06 },
491
+ className: "ash-surface relative rounded-2xl border border-white/[0.08] p-4 overflow-hidden",
492
+ children: [
493
+ /* @__PURE__ */ jsx7("div", { className: "absolute top-0 left-0 right-0 h-px", style: { background: `rgba(${rgb},0.6)` } }),
494
+ Icon && /* @__PURE__ */ jsx7("div", { className: "w-8 h-8 rounded-xl flex items-center justify-center mb-3", style: { background: `rgba(${rgb},0.15)` }, children: /* @__PURE__ */ jsx7(Icon, { size: 14, style: { color: `rgb(${rgb})` } }) }),
495
+ /* @__PURE__ */ jsx7("div", { className: "text-2xl font-black mb-1", style: { color: `rgb(${rgb})`, filter: `drop-shadow(0 0 10px rgba(${rgb},0.45))` }, children: shown }),
496
+ /* @__PURE__ */ jsx7("div", { className: "text-[11px] font-bold text-white/70", children: label }),
497
+ sub && /* @__PURE__ */ jsx7("div", { className: "text-[10px] text-white/30 mt-0.5", children: sub })
498
+ ]
499
+ }
500
+ );
501
+ }
502
+
503
+ // src/canvas/CanvasTransition.tsx
504
+ import { usePathname } from "next/navigation";
505
+ import { useEffect as useEffect4, useState as useState4 } from "react";
506
+ import { motion as motion8, AnimatePresence } from "framer-motion";
507
+ import { Sparkles } from "lucide-react";
508
+ import { Fragment, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
509
+ function CanvasTransition({
510
+ children,
511
+ labels = {},
512
+ seenKey = "ash-seen-views",
513
+ assemblerName = "Shift"
514
+ }) {
515
+ const pathname = usePathname();
516
+ const [mounted, setMounted] = useState4(false);
517
+ const [showSheen, setShowSheen] = useState4(false);
518
+ const [seen] = useState4(() => /* @__PURE__ */ new Set());
519
+ function viewLabel(path) {
520
+ const seg = path.split("/").filter(Boolean);
521
+ const key = seg.find((s) => labels[s]) ?? seg[0] ?? "dashboard";
522
+ return labels[key] ?? key.replace(/-/g, " ");
523
+ }
524
+ useEffect4(() => {
525
+ try {
526
+ const raw = sessionStorage.getItem(seenKey);
527
+ if (raw) JSON.parse(raw).forEach((p) => seen.add(p));
528
+ } catch {
529
+ }
530
+ setMounted(true);
531
+ }, []);
532
+ const firstVisit = mounted && !seen.has(pathname);
533
+ useEffect4(() => {
534
+ if (!mounted) return;
535
+ const wasFirst = !seen.has(pathname);
536
+ seen.add(pathname);
537
+ try {
538
+ sessionStorage.setItem(seenKey, JSON.stringify([...seen]));
539
+ } catch {
540
+ }
541
+ if (wasFirst) {
542
+ setShowSheen(true);
543
+ const t = setTimeout(() => setShowSheen(false), 900);
544
+ return () => clearTimeout(t);
545
+ }
546
+ setShowSheen(false);
547
+ }, [pathname, mounted]);
548
+ return /* @__PURE__ */ jsx8(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsxs5(
549
+ motion8.div,
550
+ {
551
+ initial: firstVisit ? { opacity: 0, y: 14, filter: "blur(7px)" } : { opacity: 0 },
552
+ animate: { opacity: 1, y: 0, filter: "blur(0px)" },
553
+ exit: { opacity: 0, filter: "blur(4px)", transition: { duration: 0.16 } },
554
+ transition: firstVisit ? { type: "spring", stiffness: 240, damping: 28 } : { duration: 0.16, ease: "easeOut" },
555
+ className: "relative min-h-full",
556
+ children: [
557
+ /* @__PURE__ */ jsx8(AnimatePresence, { children: showSheen && /* @__PURE__ */ jsxs5(Fragment, { children: [
558
+ /* @__PURE__ */ jsx8(
559
+ motion8.div,
560
+ {
561
+ initial: { scaleX: 0, opacity: 0.8 },
562
+ animate: { scaleX: 1, opacity: 0 },
563
+ exit: { opacity: 0 },
564
+ transition: { duration: 0.9, ease: "easeOut" },
565
+ style: {
566
+ transformOrigin: "left",
567
+ background: "linear-gradient(90deg, transparent, var(--shift-accent), transparent)"
568
+ },
569
+ className: "pointer-events-none fixed top-0 left-0 right-0 h-0.5 z-[60]"
570
+ }
571
+ ),
572
+ /* @__PURE__ */ jsxs5(
573
+ motion8.div,
574
+ {
575
+ initial: { opacity: 0, y: -6 },
576
+ animate: { opacity: 1, y: 0 },
577
+ exit: { opacity: 0, y: -6 },
578
+ transition: { duration: 0.25 },
579
+ style: {
580
+ borderColor: "color-mix(in srgb, var(--shift-accent) 25%, transparent)",
581
+ boxShadow: "0 0 24px color-mix(in srgb, var(--shift-accent) 18%, transparent)"
582
+ },
583
+ className: "ash-surface pointer-events-none fixed top-3 left-1/2 -translate-x-1/2 z-[60] flex items-center gap-1.5 rounded-full border px-3 py-1",
584
+ children: [
585
+ /* @__PURE__ */ jsx8(Sparkles, { size: 10, style: { color: "var(--shift-accent)" }, className: "animate-pulse" }),
586
+ /* @__PURE__ */ jsxs5("span", { className: "text-[10px] font-bold", style: { color: "var(--shift-accent)" }, children: [
587
+ assemblerName,
588
+ " is assembling ",
589
+ viewLabel(pathname),
590
+ "\u2026"
591
+ ] })
592
+ ]
593
+ }
594
+ )
595
+ ] }) }),
596
+ children
597
+ ]
598
+ },
599
+ pathname
600
+ ) });
601
+ }
602
+
603
+ // src/canvas/CanvasLauncher.tsx
604
+ import { useEffect as useEffect5, useMemo, useState as useState5 } from "react";
605
+ import { useRouter, usePathname as usePathname2 } from "next/navigation";
606
+ import { motion as motion9, AnimatePresence as AnimatePresence2 } from "framer-motion";
607
+ import { LayoutGrid, X, Search, Sparkles as Sparkles2 } from "lucide-react";
608
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
609
+ function CanvasLauncher({
610
+ groups,
611
+ title = "Everything Shift can build",
612
+ subtitle = "Pick a resource \u2014 I'll bring it to the canvas",
613
+ buttonLabel = "Everything",
614
+ hotkey = "j",
615
+ openEvent = "shift:open-launcher",
616
+ onNavigate
617
+ }) {
618
+ const router = useRouter();
619
+ const pathname = usePathname2();
620
+ const [open, setOpen] = useState5(false);
621
+ const [query, setQuery] = useState5("");
622
+ useEffect5(() => {
623
+ function onKey(e) {
624
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === hotkey) {
625
+ e.preventDefault();
626
+ setOpen((v) => !v);
627
+ }
628
+ if (e.key === "Escape") setOpen(false);
629
+ }
630
+ document.addEventListener("keydown", onKey);
631
+ return () => document.removeEventListener("keydown", onKey);
632
+ }, [hotkey]);
633
+ useEffect5(() => {
634
+ function onOpen() {
635
+ setOpen(true);
636
+ }
637
+ window.addEventListener(openEvent, onOpen);
638
+ return () => window.removeEventListener(openEvent, onOpen);
639
+ }, [openEvent]);
640
+ const filtered = useMemo(() => {
641
+ if (!query.trim()) return groups;
642
+ const q = query.toLowerCase();
643
+ return groups.map((g) => ({
644
+ ...g,
645
+ items: g.items.filter((i) => i.label.toLowerCase().includes(q) || i.desc.toLowerCase().includes(q))
646
+ })).filter((g) => g.items.length > 0);
647
+ }, [query, groups]);
648
+ function go(href) {
649
+ setOpen(false);
650
+ setQuery("");
651
+ if (href === pathname) return;
652
+ if (onNavigate) onNavigate(href);
653
+ else router.push(href);
654
+ }
655
+ let flatIndex = 0;
656
+ return /* @__PURE__ */ jsxs6(Fragment2, { children: [
657
+ /* @__PURE__ */ jsxs6(
658
+ "button",
659
+ {
660
+ type: "button",
661
+ onClick: () => setOpen(true),
662
+ "aria-label": "Open menu",
663
+ title: `Everything (\u2318${hotkey.toUpperCase()})`,
664
+ style: { borderColor: "color-mix(in srgb, var(--shift-accent) 20%, transparent)", color: "var(--shift-accent)" },
665
+ className: "ash-surface flex items-center gap-2 px-2.5 py-1.5 rounded-xl border transition-all hover:brightness-110",
666
+ children: [
667
+ /* @__PURE__ */ jsx9(LayoutGrid, { size: 15 }),
668
+ /* @__PURE__ */ jsx9("span", { className: "hidden lg:inline text-xs font-bold", children: buttonLabel })
669
+ ]
670
+ }
671
+ ),
672
+ /* @__PURE__ */ jsx9(AnimatePresence2, { children: open && /* @__PURE__ */ jsxs6(
673
+ motion9.div,
674
+ {
675
+ initial: { opacity: 0 },
676
+ animate: { opacity: 1 },
677
+ exit: { opacity: 0 },
678
+ transition: { duration: 0.2 },
679
+ className: "fixed inset-0 z-[8000] flex flex-col",
680
+ style: { background: "rgba(2,4,10,0.86)", backdropFilter: "blur(20px)", WebkitBackdropFilter: "blur(20px)" },
681
+ onClick: () => setOpen(false),
682
+ children: [
683
+ /* @__PURE__ */ jsx9("div", { className: "shrink-0 px-6 pt-6 pb-4", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs6("div", { className: "max-w-6xl mx-auto", children: [
684
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between mb-4", children: [
685
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
686
+ /* @__PURE__ */ jsx9(
687
+ "div",
688
+ {
689
+ className: "w-7 h-7 rounded-full flex items-center justify-center",
690
+ style: { background: "linear-gradient(135deg, var(--shift-accent), var(--shift-accent-2))", boxShadow: "0 0 14px color-mix(in srgb, var(--shift-accent) 50%, transparent)" },
691
+ children: /* @__PURE__ */ jsx9(Sparkles2, { size: 13, className: "text-white" })
692
+ }
693
+ ),
694
+ /* @__PURE__ */ jsxs6("div", { children: [
695
+ /* @__PURE__ */ jsx9("div", { className: "text-sm font-black text-white leading-none", children: title }),
696
+ /* @__PURE__ */ jsx9("div", { className: "text-[11px] text-white/40 mt-1", children: subtitle })
697
+ ] })
698
+ ] }),
699
+ /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => setOpen(false), className: "w-8 h-8 rounded-xl flex items-center justify-center text-white/40 hover:text-white hover:bg-white/[0.08] transition-all", children: /* @__PURE__ */ jsx9(X, { size: 16 }) })
700
+ ] }),
701
+ /* @__PURE__ */ jsxs6("div", { className: "ash-surface flex items-center gap-2.5 rounded-2xl border border-white/10 px-4 py-3", children: [
702
+ /* @__PURE__ */ jsx9(Search, { size: 15, className: "text-white/30" }),
703
+ /* @__PURE__ */ jsx9(
704
+ "input",
705
+ {
706
+ autoFocus: true,
707
+ value: query,
708
+ onChange: (e) => setQuery(e.target.value),
709
+ placeholder: "Search resources\u2026",
710
+ className: "flex-1 bg-transparent text-sm text-white placeholder:text-white/25 outline-none"
711
+ }
712
+ ),
713
+ /* @__PURE__ */ jsx9("kbd", { className: "text-[10px] text-white/25 border border-white/10 rounded px-1.5 py-0.5", children: "esc" })
714
+ ] })
715
+ ] }) }),
716
+ /* @__PURE__ */ jsx9("div", { className: "flex-1 overflow-y-auto px-6 pb-10", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs6("div", { className: "max-w-6xl mx-auto flex flex-col gap-7", children: [
717
+ filtered.map((group) => {
718
+ const rgb = ACCENT_RGB[group.accent];
719
+ return /* @__PURE__ */ jsxs6("div", { children: [
720
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5 mb-3", style: { color: `rgb(${rgb})` }, children: [
721
+ /* @__PURE__ */ jsx9("div", { className: "w-1.5 h-1.5 rounded-full", style: { background: `rgb(${rgb})` } }),
722
+ /* @__PURE__ */ jsx9("span", { className: "text-[11px] font-black uppercase tracking-widest", children: group.label })
723
+ ] }),
724
+ /* @__PURE__ */ jsx9("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: group.items.map((item) => {
725
+ const Icon = item.icon;
726
+ const isActive = pathname === item.href;
727
+ const idx = flatIndex++;
728
+ return /* @__PURE__ */ jsxs6(
729
+ motion9.button,
730
+ {
731
+ type: "button",
732
+ onClick: () => go(item.href),
733
+ initial: { opacity: 0, y: 14, filter: "blur(5px)" },
734
+ animate: { opacity: 1, y: 0, filter: "blur(0px)" },
735
+ transition: { duration: 0.28, delay: Math.min(idx * 0.018, 0.5), ease: "easeOut" },
736
+ whileHover: { y: -3, scale: 1.015 },
737
+ style: isActive ? { borderColor: `rgba(${rgb},0.4)` } : void 0,
738
+ className: `ash-surface group relative text-left rounded-2xl border p-4 overflow-hidden transition-colors ${isActive ? "" : "border-white/[0.08] hover:border-white/[0.15]"}`,
739
+ children: [
740
+ /* @__PURE__ */ jsx9("div", { className: "absolute top-0 left-0 right-0 h-px opacity-60", style: { background: `rgba(${rgb},0.7)` } }),
741
+ /* @__PURE__ */ jsx9("div", { className: "w-9 h-9 rounded-xl flex items-center justify-center mb-3", style: { background: `rgba(${rgb},0.12)` }, children: /* @__PURE__ */ jsx9(Icon, { size: 16, style: { color: `rgb(${rgb})` } }) }),
742
+ /* @__PURE__ */ jsx9("div", { className: "text-sm font-bold text-white mb-0.5", children: item.label }),
743
+ /* @__PURE__ */ jsx9("div", { className: "text-[11px] text-white/40 leading-snug", children: item.desc }),
744
+ isActive && /* @__PURE__ */ jsx9("span", { className: "absolute top-3 right-3 text-[9px] font-bold", style: { color: `rgb(${rgb})` }, children: "on canvas" }),
745
+ /* @__PURE__ */ jsx9("div", { className: "absolute bottom-0 right-0 w-24 h-24 rounded-full blur-2xl opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none", style: { background: `rgba(${rgb},0.18)` } })
746
+ ]
747
+ },
748
+ item.href
749
+ );
750
+ }) })
751
+ ] }, group.label);
752
+ }),
753
+ filtered.length === 0 && /* @__PURE__ */ jsxs6("div", { className: "text-center text-white/30 text-sm py-20", children: [
754
+ "No resources match \u201C",
755
+ query,
756
+ "\u201D"
757
+ ] })
758
+ ] }) })
759
+ ]
760
+ }
761
+ ) })
762
+ ] });
763
+ }
764
+
765
+ // src/canvas/PageTransition.tsx
766
+ import { motion as motion10 } from "framer-motion";
767
+ import { usePathname as usePathname3 } from "next/navigation";
768
+ import { useRef as useRef5 } from "react";
769
+ import { jsx as jsx10 } from "react/jsx-runtime";
770
+ function PageTransition({ children, className }) {
771
+ const pathname = usePathname3();
772
+ const depthRef = useRef5(0);
773
+ const dirRef = useRef5(0);
774
+ const depth = pathname.split("/").filter(Boolean).length;
775
+ if (depth !== depthRef.current) {
776
+ dirRef.current = dirRef.current === 0 ? 0 : depth >= depthRef.current ? 1 : -1;
777
+ depthRef.current = depth;
778
+ }
779
+ const dir = dirRef.current;
780
+ return /* @__PURE__ */ jsx10(
781
+ motion10.div,
782
+ {
783
+ initial: { opacity: 0, x: dir * 22, filter: "blur(3px)" },
784
+ animate: { opacity: 1, x: 0, filter: "blur(0px)" },
785
+ transition: { type: "spring", stiffness: 400, damping: 34 },
786
+ className: className ?? "flex flex-col flex-1 min-h-0",
787
+ children
788
+ },
789
+ pathname
790
+ );
791
+ }
206
792
  export {
793
+ ACCENT_RGB,
794
+ CanvasLauncher,
795
+ CanvasTransition,
796
+ GenerativeView,
207
797
  HolographicShine,
798
+ LiveCard,
799
+ LiveStat,
800
+ PageTransition,
208
801
  ScrollGenerateCard,
209
802
  ShiftCard,
210
- TiltCard
803
+ TiltCard,
804
+ useGenerate
211
805
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@allshift/ui",
3
- "version": "0.2.0",
4
- "description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation. Brand-neutral by design.",
3
+ "version": "0.3.0",
4
+ "description": "Shift family shared FEEL layer — glass morphism, 3D tilt physics, holographic shine, scroll-triggered generation, and the living-canvas engine (LiveCard, LiveStat, CanvasTransition, CanvasLauncher). Brand-neutral by design.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
7
7
  "type": "module",
@@ -21,6 +21,7 @@
21
21
  "require": "./dist/index.cjs"
22
22
  },
23
23
  "./styles.css": "./styles/shift-ui.css",
24
+ "./canvas.css": "./styles/shift-canvas.css",
24
25
  "./package.json": "./package.json"
25
26
  },
26
27
  "scripts": {
@@ -30,11 +31,19 @@
30
31
  },
31
32
  "peerDependencies": {
32
33
  "framer-motion": ">=10",
33
- "react": ">=18"
34
+ "react": ">=18",
35
+ "lucide-react": ">=0.300.0",
36
+ "next": ">=14"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "lucide-react": { "optional": true },
40
+ "next": { "optional": true }
34
41
  },
35
42
  "devDependencies": {
36
43
  "@types/react": "^19.2.17",
37
44
  "framer-motion": "^12.40.0",
45
+ "lucide-react": "^1.14.0",
46
+ "next": "^16.2.7",
38
47
  "react": "^19.2.7",
39
48
  "tsup": "^8.5.1",
40
49
  "typescript": "^6.0.3"