@medialane/ui 0.60.0 → 0.62.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 (40) hide show
  1. package/dist/components/asset-card.cjs +1 -8
  2. package/dist/components/asset-card.cjs.map +1 -1
  3. package/dist/components/asset-card.js +1 -8
  4. package/dist/components/asset-card.js.map +1 -1
  5. package/dist/components/coin-launch-preview.cjs +1 -8
  6. package/dist/components/coin-launch-preview.cjs.map +1 -1
  7. package/dist/components/coin-launch-preview.js +1 -8
  8. package/dist/components/coin-launch-preview.js.map +1 -1
  9. package/dist/components/medialane-collection-card.cjs +1 -8
  10. package/dist/components/medialane-collection-card.cjs.map +1 -1
  11. package/dist/components/medialane-collection-card.js +1 -8
  12. package/dist/components/medialane-collection-card.js.map +1 -1
  13. package/dist/components/nav-command-menu.cjs +167 -51
  14. package/dist/components/nav-command-menu.cjs.map +1 -1
  15. package/dist/components/nav-command-menu.d.cts +2 -0
  16. package/dist/components/nav-command-menu.d.ts +2 -0
  17. package/dist/components/nav-command-menu.js +167 -51
  18. package/dist/components/nav-command-menu.js.map +1 -1
  19. package/dist/components/nav-shell.cjs +195 -0
  20. package/dist/components/nav-shell.cjs.map +1 -0
  21. package/dist/components/nav-shell.d.cts +59 -0
  22. package/dist/components/nav-shell.d.ts +59 -0
  23. package/dist/components/nav-shell.js +157 -0
  24. package/dist/components/nav-shell.js.map +1 -0
  25. package/dist/components/portfolio-header.cjs +5 -8
  26. package/dist/components/portfolio-header.cjs.map +1 -1
  27. package/dist/components/portfolio-header.js +6 -9
  28. package/dist/components/portfolio-header.js.map +1 -1
  29. package/dist/components/portfolio-overview.cjs +28 -32
  30. package/dist/components/portfolio-overview.cjs.map +1 -1
  31. package/dist/components/portfolio-overview.js +29 -33
  32. package/dist/components/portfolio-overview.js.map +1 -1
  33. package/dist/index.cjs +11 -0
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +1 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +12 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/medialane.css +4 -3
  40. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ interface MedialaneMarkProps {
5
+ size?: number;
6
+ className?: string;
7
+ }
8
+ /**
9
+ * The Medialane symbol as an inline SVG — electric-blue squircle, sparkle,
10
+ * twin bars, rose→orange dot. Replaces `<img src="/icon.png">` triggers so the
11
+ * mark renders crisp at any size, in any host, with no asset path to break.
12
+ */
13
+ declare function MedialaneMark({ size, className }: MedialaneMarkProps): react_jsx_runtime.JSX.Element;
14
+ interface NavBrandButtonProps {
15
+ onClick?: () => void;
16
+ className?: string;
17
+ "aria-label"?: string;
18
+ }
19
+ /**
20
+ * The main header trigger: brand mark + menu glyph in one quiet glass pill.
21
+ * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.
22
+ */
23
+ declare function NavBrandButton({ onClick, className, ...rest }: NavBrandButtonProps): react_jsx_runtime.JSX.Element;
24
+ interface NavIconButtonProps {
25
+ onClick?: () => void;
26
+ className?: string;
27
+ "aria-label": string;
28
+ /** Show a small brand-colored status dot (e.g. wallet connected). */
29
+ indicator?: boolean;
30
+ children: React.ReactNode;
31
+ }
32
+ /**
33
+ * Circular glass header button — the right-side counterpart to NavBrandButton
34
+ * (wallet / account trigger).
35
+ */
36
+ declare function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps): react_jsx_runtime.JSX.Element;
37
+ declare function useNavAccountSheet(): {
38
+ open: () => boolean;
39
+ close: () => boolean;
40
+ };
41
+ interface NavAccountSheetProps {
42
+ /** The app's account/connect panel (Clerk, wallet connectors, …). */
43
+ children: React.ReactNode;
44
+ /** Optional heading above the panel. */
45
+ title?: string;
46
+ }
47
+ /**
48
+ * The wallet-side surface: a compact glass sheet anchored to the top-right
49
+ * header button (bottom sheet on mobile). Content is app-owned — pass the same
50
+ * account panel the command menu uses in its accountSlot.
51
+ *
52
+ * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the command
53
+ * menu: opening one closes the other, and the shared `ml:nav-close` event
54
+ * (fired by `useNavCommandMenu().close()`) closes this sheet too, so account
55
+ * panels behave identically in both hosts.
56
+ */
57
+ declare function NavAccountSheet({ children, title }: NavAccountSheetProps): react_jsx_runtime.JSX.Element;
58
+
59
+ export { MedialaneMark, type MedialaneMarkProps, NavAccountSheet, type NavAccountSheetProps, NavBrandButton, type NavBrandButtonProps, NavIconButton, type NavIconButtonProps, useNavAccountSheet };
@@ -0,0 +1,59 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import * as React from 'react';
3
+
4
+ interface MedialaneMarkProps {
5
+ size?: number;
6
+ className?: string;
7
+ }
8
+ /**
9
+ * The Medialane symbol as an inline SVG — electric-blue squircle, sparkle,
10
+ * twin bars, rose→orange dot. Replaces `<img src="/icon.png">` triggers so the
11
+ * mark renders crisp at any size, in any host, with no asset path to break.
12
+ */
13
+ declare function MedialaneMark({ size, className }: MedialaneMarkProps): react_jsx_runtime.JSX.Element;
14
+ interface NavBrandButtonProps {
15
+ onClick?: () => void;
16
+ className?: string;
17
+ "aria-label"?: string;
18
+ }
19
+ /**
20
+ * The main header trigger: brand mark + menu glyph in one quiet glass pill.
21
+ * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.
22
+ */
23
+ declare function NavBrandButton({ onClick, className, ...rest }: NavBrandButtonProps): react_jsx_runtime.JSX.Element;
24
+ interface NavIconButtonProps {
25
+ onClick?: () => void;
26
+ className?: string;
27
+ "aria-label": string;
28
+ /** Show a small brand-colored status dot (e.g. wallet connected). */
29
+ indicator?: boolean;
30
+ children: React.ReactNode;
31
+ }
32
+ /**
33
+ * Circular glass header button — the right-side counterpart to NavBrandButton
34
+ * (wallet / account trigger).
35
+ */
36
+ declare function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps): react_jsx_runtime.JSX.Element;
37
+ declare function useNavAccountSheet(): {
38
+ open: () => boolean;
39
+ close: () => boolean;
40
+ };
41
+ interface NavAccountSheetProps {
42
+ /** The app's account/connect panel (Clerk, wallet connectors, …). */
43
+ children: React.ReactNode;
44
+ /** Optional heading above the panel. */
45
+ title?: string;
46
+ }
47
+ /**
48
+ * The wallet-side surface: a compact glass sheet anchored to the top-right
49
+ * header button (bottom sheet on mobile). Content is app-owned — pass the same
50
+ * account panel the command menu uses in its accountSlot.
51
+ *
52
+ * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the command
53
+ * menu: opening one closes the other, and the shared `ml:nav-close` event
54
+ * (fired by `useNavCommandMenu().close()`) closes this sheet too, so account
55
+ * panels behave identically in both hosts.
56
+ */
57
+ declare function NavAccountSheet({ children, title }: NavAccountSheetProps): react_jsx_runtime.JSX.Element;
58
+
59
+ export { MedialaneMark, type MedialaneMarkProps, NavAccountSheet, type NavAccountSheetProps, NavBrandButton, type NavBrandButtonProps, NavIconButton, type NavIconButtonProps, useNavAccountSheet };
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { AnimatePresence, motion } from "framer-motion";
5
+ import { cn } from "../utils/cn.js";
6
+ function MedialaneMark({ size = 32, className }) {
7
+ const gradId = React.useId();
8
+ return /* @__PURE__ */ jsxs(
9
+ "svg",
10
+ {
11
+ width: size,
12
+ height: size,
13
+ viewBox: "0 0 32 32",
14
+ fill: "none",
15
+ className,
16
+ "aria-hidden": "true",
17
+ children: [
18
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradId, x1: "20", y1: "20", x2: "27", y2: "27", gradientUnits: "userSpaceOnUse", children: [
19
+ /* @__PURE__ */ jsx("stop", { offset: "0", stopColor: "#f6608f" }),
20
+ /* @__PURE__ */ jsx("stop", { offset: "1", stopColor: "#fb8b46" })
21
+ ] }) }),
22
+ /* @__PURE__ */ jsx("rect", { x: "2", y: "2", width: "28", height: "28", rx: "9", fill: "#1a17ff" }),
23
+ /* @__PURE__ */ jsx("path", { d: "M9 6.2 9.9 8.4 12.1 9.3 9.9 10.2 9 12.4 8.1 10.2 5.9 9.3 8.1 8.4Z", fill: "#fff" }),
24
+ /* @__PURE__ */ jsx("rect", { x: "14.1", y: "4.5", width: "1.9", height: "23", rx: "0.9", fill: "#fff" }),
25
+ /* @__PURE__ */ jsx("rect", { x: "17.3", y: "4.5", width: "1.9", height: "23", rx: "0.9", fill: "#fff" }),
26
+ /* @__PURE__ */ jsx("circle", { cx: "23.4", cy: "22.6", r: "2.7", fill: `url(#${gradId})` })
27
+ ]
28
+ }
29
+ );
30
+ }
31
+ function NavBrandButton({ onClick, className, ...rest }) {
32
+ return /* @__PURE__ */ jsxs(
33
+ "button",
34
+ {
35
+ type: "button",
36
+ onClick,
37
+ "aria-label": rest["aria-label"] ?? "Open navigation",
38
+ className: cn(
39
+ "group flex h-11 items-center gap-2 rounded-full border border-border/40",
40
+ "bg-background/70 pl-1.5 pr-3 backdrop-blur-xl backdrop-saturate-150 shadow-sm",
41
+ "transition-colors hover:bg-background/90 active:scale-[0.97]",
42
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
43
+ className
44
+ ),
45
+ children: [
46
+ /* @__PURE__ */ jsx(MedialaneMark, { size: 30, className: "shrink-0" }),
47
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-col gap-[5px]", "aria-hidden": "true", children: [
48
+ /* @__PURE__ */ jsx("span", { className: "h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground" }),
49
+ /* @__PURE__ */ jsx("span", { className: "h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground" })
50
+ ] })
51
+ ]
52
+ }
53
+ );
54
+ }
55
+ function NavIconButton({ onClick, className, indicator, children, ...rest }) {
56
+ return /* @__PURE__ */ jsxs(
57
+ "button",
58
+ {
59
+ type: "button",
60
+ onClick,
61
+ "aria-label": rest["aria-label"],
62
+ className: cn(
63
+ "relative flex h-11 w-11 items-center justify-center rounded-full border border-border/40",
64
+ "bg-background/70 text-muted-foreground backdrop-blur-xl backdrop-saturate-150 shadow-sm",
65
+ "transition-colors hover:bg-background/90 hover:text-foreground active:scale-[0.97]",
66
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60",
67
+ className
68
+ ),
69
+ children: [
70
+ children,
71
+ indicator && /* @__PURE__ */ jsx("span", { className: "absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue" })
72
+ ]
73
+ }
74
+ );
75
+ }
76
+ const ML_ACCOUNT_OPEN = "ml:account-open";
77
+ const ML_ACCOUNT_CLOSE = "ml:account-close";
78
+ const ML_NAV_OPEN = "ml:nav-open";
79
+ const ML_NAV_CLOSE = "ml:nav-close";
80
+ function useNavAccountSheet() {
81
+ return {
82
+ open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),
83
+ close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE))
84
+ };
85
+ }
86
+ function NavAccountSheet({ children, title = "Account" }) {
87
+ const [open, setOpen] = React.useState(false);
88
+ React.useEffect(() => {
89
+ const onOpen = () => {
90
+ document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));
91
+ setOpen(true);
92
+ };
93
+ const onClose = () => setOpen(false);
94
+ const onKey = (e) => {
95
+ if (e.key === "Escape") setOpen(false);
96
+ };
97
+ document.addEventListener(ML_ACCOUNT_OPEN, onOpen);
98
+ document.addEventListener(ML_ACCOUNT_CLOSE, onClose);
99
+ document.addEventListener(ML_NAV_OPEN, onClose);
100
+ document.addEventListener(ML_NAV_CLOSE, onClose);
101
+ document.addEventListener("keydown", onKey);
102
+ return () => {
103
+ document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);
104
+ document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);
105
+ document.removeEventListener(ML_NAV_OPEN, onClose);
106
+ document.removeEventListener(ML_NAV_CLOSE, onClose);
107
+ document.removeEventListener("keydown", onKey);
108
+ };
109
+ }, []);
110
+ return /* @__PURE__ */ jsx(AnimatePresence, { children: open && /* @__PURE__ */ jsxs(Fragment, { children: [
111
+ /* @__PURE__ */ jsx(
112
+ motion.div,
113
+ {
114
+ className: "nav-canvas-overlay",
115
+ initial: { opacity: 0 },
116
+ animate: { opacity: 1 },
117
+ exit: { opacity: 0 },
118
+ transition: { duration: 0.15 },
119
+ onClick: () => setOpen(false)
120
+ }
121
+ ),
122
+ /* @__PURE__ */ jsx(
123
+ motion.div,
124
+ {
125
+ className: "fixed inset-x-0 bottom-0 z-[101] sm:inset-auto sm:right-4 sm:top-4 sm:w-[360px] lg:right-8",
126
+ initial: { opacity: 0, y: 24 },
127
+ animate: { opacity: 1, y: 0 },
128
+ exit: { opacity: 0, y: 24 },
129
+ transition: { duration: 0.18, ease: "easeOut" },
130
+ children: /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-t-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150 sm:rounded-[20px]", children: [
131
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center pt-2.5 sm:hidden", "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "h-1 w-9 rounded-full bg-muted-foreground/30" }) }),
132
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 pb-1 pt-2 sm:pt-3", children: [
133
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70", children: title }),
134
+ /* @__PURE__ */ jsx(
135
+ "button",
136
+ {
137
+ onClick: () => setOpen(false),
138
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted/50",
139
+ "aria-label": "Close",
140
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" }) })
141
+ }
142
+ )
143
+ ] }),
144
+ /* @__PURE__ */ jsx("div", { className: "px-3 pb-4 pt-1 sm:pb-3", children })
145
+ ] })
146
+ }
147
+ )
148
+ ] }) });
149
+ }
150
+ export {
151
+ MedialaneMark,
152
+ NavAccountSheet,
153
+ NavBrandButton,
154
+ NavIconButton,
155
+ useNavAccountSheet
156
+ };
157
+ //# sourceMappingURL=nav-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/nav-shell.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { AnimatePresence, motion } from \"framer-motion\";\nimport { cn } from \"../utils/cn.js\";\n\n// ── Brand mark ────────────────────────────────────────────────────────────────\n\nexport interface MedialaneMarkProps {\n size?: number;\n className?: string;\n}\n\n/**\n * The Medialane symbol as an inline SVG — electric-blue squircle, sparkle,\n * twin bars, rose→orange dot. Replaces `<img src=\"/icon.png\">` triggers so the\n * mark renders crisp at any size, in any host, with no asset path to break.\n */\nexport function MedialaneMark({ size = 32, className }: MedialaneMarkProps) {\n const gradId = React.useId();\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 32 32\"\n fill=\"none\"\n className={className}\n aria-hidden=\"true\"\n >\n <defs>\n <linearGradient id={gradId} x1=\"20\" y1=\"20\" x2=\"27\" y2=\"27\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0\" stopColor=\"#f6608f\" />\n <stop offset=\"1\" stopColor=\"#fb8b46\" />\n </linearGradient>\n </defs>\n <rect x=\"2\" y=\"2\" width=\"28\" height=\"28\" rx=\"9\" fill=\"#1a17ff\" />\n <path d=\"M9 6.2 9.9 8.4 12.1 9.3 9.9 10.2 9 12.4 8.1 10.2 5.9 9.3 8.1 8.4Z\" fill=\"#fff\" />\n <rect x=\"14.1\" y=\"4.5\" width=\"1.9\" height=\"23\" rx=\"0.9\" fill=\"#fff\" />\n <rect x=\"17.3\" y=\"4.5\" width=\"1.9\" height=\"23\" rx=\"0.9\" fill=\"#fff\" />\n <circle cx=\"23.4\" cy=\"22.6\" r=\"2.7\" fill={`url(#${gradId})`} />\n </svg>\n );\n}\n\n// ── Header buttons ────────────────────────────────────────────────────────────\n\nexport interface NavBrandButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\"?: string;\n}\n\n/**\n * The main header trigger: brand mark + menu glyph in one quiet glass pill.\n * Mobile-first — a single ≥44px tap target instead of two small adjacent icons.\n */\nexport function NavBrandButton({ onClick, className, ...rest }: NavBrandButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"] ?? \"Open navigation\"}\n className={cn(\n \"group flex h-11 items-center gap-2 rounded-full border border-border/40\",\n \"bg-background/70 pl-1.5 pr-3 backdrop-blur-xl backdrop-saturate-150 shadow-sm\",\n \"transition-colors hover:bg-background/90 active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n <MedialaneMark size={30} className=\"shrink-0\" />\n <span className=\"flex flex-col gap-[5px]\" aria-hidden=\"true\">\n <span className=\"h-[1.5px] w-4 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n <span className=\"h-[1.5px] w-2.5 rounded-full bg-muted-foreground transition-all group-hover:w-4 group-hover:bg-foreground\" />\n </span>\n </button>\n );\n}\n\nexport interface NavIconButtonProps {\n onClick?: () => void;\n className?: string;\n \"aria-label\": string;\n /** Show a small brand-colored status dot (e.g. wallet connected). */\n indicator?: boolean;\n children: React.ReactNode;\n}\n\n/**\n * Circular glass header button — the right-side counterpart to NavBrandButton\n * (wallet / account trigger).\n */\nexport function NavIconButton({ onClick, className, indicator, children, ...rest }: NavIconButtonProps) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n aria-label={rest[\"aria-label\"]}\n className={cn(\n \"relative flex h-11 w-11 items-center justify-center rounded-full border border-border/40\",\n \"bg-background/70 text-muted-foreground backdrop-blur-xl backdrop-saturate-150 shadow-sm\",\n \"transition-colors hover:bg-background/90 hover:text-foreground active:scale-[0.97]\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/60\",\n className\n )}\n >\n {children}\n {indicator && (\n <span className=\"absolute right-[3px] top-[3px] h-2.5 w-2.5 rounded-full border-2 border-background bg-brand-blue\" />\n )}\n </button>\n );\n}\n\n// ── Account sheet ─────────────────────────────────────────────────────────────\n\nconst ML_ACCOUNT_OPEN = \"ml:account-open\";\nconst ML_ACCOUNT_CLOSE = \"ml:account-close\";\nconst ML_NAV_OPEN = \"ml:nav-open\";\nconst ML_NAV_CLOSE = \"ml:nav-close\";\n\nexport function useNavAccountSheet() {\n return {\n open: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_OPEN)),\n close: () => document.dispatchEvent(new CustomEvent(ML_ACCOUNT_CLOSE)),\n };\n}\n\nexport interface NavAccountSheetProps {\n /** The app's account/connect panel (Clerk, wallet connectors, …). */\n children: React.ReactNode;\n /** Optional heading above the panel. */\n title?: string;\n}\n\n/**\n * The wallet-side surface: a compact glass sheet anchored to the top-right\n * header button (bottom sheet on mobile). Content is app-owned — pass the same\n * account panel the command menu uses in its accountSlot.\n *\n * Opens via `useNavAccountSheet().open()`. Mutually exclusive with the command\n * menu: opening one closes the other, and the shared `ml:nav-close` event\n * (fired by `useNavCommandMenu().close()`) closes this sheet too, so account\n * panels behave identically in both hosts.\n */\nexport function NavAccountSheet({ children, title = \"Account\" }: NavAccountSheetProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onOpen = () => {\n document.dispatchEvent(new CustomEvent(ML_NAV_CLOSE));\n setOpen(true);\n };\n const onClose = () => setOpen(false);\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.addEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.addEventListener(ML_NAV_OPEN, onClose);\n document.addEventListener(ML_NAV_CLOSE, onClose);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(ML_ACCOUNT_OPEN, onOpen);\n document.removeEventListener(ML_ACCOUNT_CLOSE, onClose);\n document.removeEventListener(ML_NAV_OPEN, onClose);\n document.removeEventListener(ML_NAV_CLOSE, onClose);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, []);\n\n return (\n <AnimatePresence>\n {open && (\n <>\n <motion.div\n className=\"nav-canvas-overlay\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1 }}\n exit={{ opacity: 0 }}\n transition={{ duration: 0.15 }}\n onClick={() => setOpen(false)}\n />\n <motion.div\n className=\"fixed inset-x-0 bottom-0 z-[101] sm:inset-auto sm:right-4 sm:top-4 sm:w-[360px] lg:right-8\"\n initial={{ opacity: 0, y: 24 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: 24 }}\n transition={{ duration: 0.18, ease: \"easeOut\" }}\n >\n <div className=\"overflow-hidden rounded-t-[20px] border border-border/40 bg-background/90 shadow-2xl backdrop-blur-2xl backdrop-saturate-150 sm:rounded-[20px]\">\n <div className=\"flex justify-center pt-2.5 sm:hidden\" aria-hidden=\"true\">\n <span className=\"h-1 w-9 rounded-full bg-muted-foreground/30\" />\n </div>\n <div className=\"flex items-center justify-between px-4 pb-1 pt-2 sm:pt-3\">\n <span className=\"text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/70\">\n {title}\n </span>\n <button\n onClick={() => setOpen(false)}\n className=\"rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted/50\"\n aria-label=\"Close\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n </div>\n <div className=\"px-3 pb-4 pt-1 sm:pb-3\">{children}</div>\n </div>\n </motion.div>\n </>\n )}\n </AnimatePresence>\n );\n}\n"],"mappings":"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","names":[]}
@@ -42,14 +42,11 @@ function PortfolioHeader({
42
42
  score,
43
43
  className
44
44
  }) {
45
- const scoreChip = score && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btn-border-animated p-[1.5px] rounded-2xl", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-[14px] bg-card px-4 py-2.5 flex items-center gap-3", children: [
46
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-8 w-8 rounded-full shrink-0 flex items-center justify-center bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Sparkles, { className: "h-4 w-4 text-white" }) }),
47
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0 leading-tight", children: [
48
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm font-bold gradient-text-full truncate", children: score.levelName }),
49
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-[11px] text-muted-foreground tabular-nums", children: [
50
- score.totalXp.toLocaleString(),
51
- " XP"
52
- ] })
45
+ const scoreChip = score && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btn-border-animated p-[1.5px] rounded-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-full bg-card px-3.5 py-1.5 flex items-baseline gap-2 whitespace-nowrap", children: [
46
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-semibold text-foreground", children: score.levelName }),
47
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
48
+ score.totalXp.toLocaleString(),
49
+ " XP"
53
50
  ] })
54
51
  ] }) });
55
52
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/portfolio-header.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { Briefcase, Sparkles } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface PortfolioHeaderScore {\n /** Journey level name (\"Starter\", \"Explorer\", …) — no numeric level shown. */\n levelName: string;\n totalXp: number;\n /** Where the score chip links (\"/rewards\"). */\n href?: string;\n}\n\nexport interface PortfolioHeaderProps {\n address: string;\n score?: PortfolioHeaderScore | null;\n className?: string;\n}\n\n/**\n * Compact portfolio header: eyebrow + address on the left, the rewards\n * journey chip on the right — level name + XP inside the animated\n * full-spectrum brand border (`.btn-border-animated`). Counts live in the\n * Overview stat tiles, not here.\n */\nexport function PortfolioHeader({\n address,\n score,\n className,\n}: PortfolioHeaderProps) {\n const scoreChip = score && (\n <div className=\"btn-border-animated p-[1.5px] rounded-2xl\">\n <div className=\"rounded-[14px] bg-card px-4 py-2.5 flex items-center gap-3\">\n <div className=\"h-8 w-8 rounded-full shrink-0 flex items-center justify-center bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\">\n <Sparkles className=\"h-4 w-4 text-white\" />\n </div>\n <div className=\"min-w-0 leading-tight\">\n <p className=\"text-sm font-bold gradient-text-full truncate\">\n {score.levelName}\n </p>\n <p className=\"text-[11px] text-muted-foreground tabular-nums\">\n {score.totalXp.toLocaleString()} XP\n </p>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className={cn(\"flex items-start justify-between gap-4\", className)}\n >\n <div className=\"space-y-1.5 min-w-0\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Briefcase className=\"h-4 w-4\" />\n <span className=\"text-xs font-semibold uppercase tracking-wider\">\n Portfolio\n </span>\n </div>\n <AddressDisplay address={address} chars={6} className=\"text-sm\" />\n </div>\n {score?.href ? (\n <Link href={score.href} className=\"shrink-0 active:opacity-80\">\n {scoreChip}\n </Link>\n ) : (\n <div className=\"shrink-0\">{scoreChip}</div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCU;AAlCV,kBAAiB;AACjB,0BAAoC;AACpC,gBAAmB;AACnB,6BAA+B;AAsBxB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,YAAY,SAChB,4CAAC,SAAI,WAAU,6CACb,uDAAC,SAAI,WAAU,8DACb;AAAA,gDAAC,SAAI,WAAU,mIACb,sDAAC,gCAAS,WAAU,sBAAqB,GAC3C;AAAA,IACA,6CAAC,SAAI,WAAU,yBACb;AAAA,kDAAC,OAAE,WAAU,iDACV,gBAAM,WACT;AAAA,MACA,6CAAC,OAAE,WAAU,kDACV;AAAA,cAAM,QAAQ,eAAe;AAAA,QAAE;AAAA,SAClC;AAAA,OACF;AAAA,KACF,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW,cAAG,0CAA0C,SAAS;AAAA,MAEjE;AAAA,qDAAC,SAAI,WAAU,uBACb;AAAA,uDAAC,SAAI,WAAU,wCACb;AAAA,wDAAC,iCAAU,WAAU,WAAU;AAAA,YAC/B,4CAAC,UAAK,WAAU,kDAAiD,uBAEjE;AAAA,aACF;AAAA,UACA,4CAAC,yCAAe,SAAkB,OAAO,GAAG,WAAU,WAAU;AAAA,WAClE;AAAA,QACC,OAAO,OACN,4CAAC,YAAAA,SAAA,EAAK,MAAM,MAAM,MAAM,WAAU,8BAC/B,qBACH,IAEA,4CAAC,SAAI,WAAU,YAAY,qBAAU;AAAA;AAAA;AAAA,EAEzC;AAEJ;","names":["Link"]}
1
+ {"version":3,"sources":["../../src/components/portfolio-header.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { Briefcase } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface PortfolioHeaderScore {\n /** Journey level name (\"Starter\", \"Explorer\", …) — no numeric level shown. */\n levelName: string;\n totalXp: number;\n /** Where the score chip links (\"/rewards\"). */\n href?: string;\n}\n\nexport interface PortfolioHeaderProps {\n address: string;\n score?: PortfolioHeaderScore | null;\n className?: string;\n}\n\n/**\n * Compact portfolio header: eyebrow + address on the left, the rewards\n * journey chip on the right — level name + XP inside the animated\n * full-spectrum brand border (`.btn-border-animated`). Counts live in the\n * Overview stat tiles, not here.\n */\nexport function PortfolioHeader({\n address,\n score,\n className,\n}: PortfolioHeaderProps) {\n const scoreChip = score && (\n <div className=\"btn-border-animated p-[1.5px] rounded-full\">\n <div className=\"rounded-full bg-card px-3.5 py-1.5 flex items-baseline gap-2 whitespace-nowrap\">\n <span className=\"text-sm font-semibold text-foreground\">\n {score.levelName}\n </span>\n <span className=\"text-xs text-muted-foreground tabular-nums\">\n {score.totalXp.toLocaleString()} XP\n </span>\n </div>\n </div>\n );\n\n return (\n <div\n className={cn(\"flex items-start justify-between gap-4\", className)}\n >\n <div className=\"space-y-1.5 min-w-0\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Briefcase className=\"h-4 w-4\" />\n <span className=\"text-xs font-semibold uppercase tracking-wider\">\n Portfolio\n </span>\n </div>\n <AddressDisplay address={address} chars={6} className=\"text-sm\" />\n </div>\n {score?.href ? (\n <Link href={score.href} className=\"shrink-0 active:opacity-80\">\n {scoreChip}\n </Link>\n ) : (\n <div className=\"shrink-0\">{scoreChip}</div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmCQ;AAjCR,kBAAiB;AACjB,0BAA0B;AAC1B,gBAAmB;AACnB,6BAA+B;AAsBxB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,YAAY,SAChB,4CAAC,SAAI,WAAU,8CACb,uDAAC,SAAI,WAAU,kFACb;AAAA,gDAAC,UAAK,WAAU,yCACb,gBAAM,WACT;AAAA,IACA,6CAAC,UAAK,WAAU,8CACb;AAAA,YAAM,QAAQ,eAAe;AAAA,MAAE;AAAA,OAClC;AAAA,KACF,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW,cAAG,0CAA0C,SAAS;AAAA,MAEjE;AAAA,qDAAC,SAAI,WAAU,uBACb;AAAA,uDAAC,SAAI,WAAU,wCACb;AAAA,wDAAC,iCAAU,WAAU,WAAU;AAAA,YAC/B,4CAAC,UAAK,WAAU,kDAAiD,uBAEjE;AAAA,aACF;AAAA,UACA,4CAAC,yCAAe,SAAkB,OAAO,GAAG,WAAU,WAAU;AAAA,WAClE;AAAA,QACC,OAAO,OACN,4CAAC,YAAAA,SAAA,EAAK,MAAM,MAAM,MAAM,WAAU,8BAC/B,qBACH,IAEA,4CAAC,SAAI,WAAU,YAAY,qBAAU;AAAA;AAAA;AAAA,EAEzC;AAEJ;","names":["Link"]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
- import { Briefcase, Sparkles } from "lucide-react";
4
+ import { Briefcase } from "lucide-react";
5
5
  import { cn } from "../utils/cn.js";
6
6
  import { AddressDisplay } from "./address-display.js";
7
7
  function PortfolioHeader({
@@ -9,14 +9,11 @@ function PortfolioHeader({
9
9
  score,
10
10
  className
11
11
  }) {
12
- const scoreChip = score && /* @__PURE__ */ jsx("div", { className: "btn-border-animated p-[1.5px] rounded-2xl", children: /* @__PURE__ */ jsxs("div", { className: "rounded-[14px] bg-card px-4 py-2.5 flex items-center gap-3", children: [
13
- /* @__PURE__ */ jsx("div", { className: "h-8 w-8 rounded-full shrink-0 flex items-center justify-center bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose", children: /* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 text-white" }) }),
14
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 leading-tight", children: [
15
- /* @__PURE__ */ jsx("p", { className: "text-sm font-bold gradient-text-full truncate", children: score.levelName }),
16
- /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground tabular-nums", children: [
17
- score.totalXp.toLocaleString(),
18
- " XP"
19
- ] })
12
+ const scoreChip = score && /* @__PURE__ */ jsx("div", { className: "btn-border-animated p-[1.5px] rounded-full", children: /* @__PURE__ */ jsxs("div", { className: "rounded-full bg-card px-3.5 py-1.5 flex items-baseline gap-2 whitespace-nowrap", children: [
13
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: score.levelName }),
14
+ /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
15
+ score.totalXp.toLocaleString(),
16
+ " XP"
20
17
  ] })
21
18
  ] }) });
22
19
  return /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/portfolio-header.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { Briefcase, Sparkles } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface PortfolioHeaderScore {\n /** Journey level name (\"Starter\", \"Explorer\", …) — no numeric level shown. */\n levelName: string;\n totalXp: number;\n /** Where the score chip links (\"/rewards\"). */\n href?: string;\n}\n\nexport interface PortfolioHeaderProps {\n address: string;\n score?: PortfolioHeaderScore | null;\n className?: string;\n}\n\n/**\n * Compact portfolio header: eyebrow + address on the left, the rewards\n * journey chip on the right — level name + XP inside the animated\n * full-spectrum brand border (`.btn-border-animated`). Counts live in the\n * Overview stat tiles, not here.\n */\nexport function PortfolioHeader({\n address,\n score,\n className,\n}: PortfolioHeaderProps) {\n const scoreChip = score && (\n <div className=\"btn-border-animated p-[1.5px] rounded-2xl\">\n <div className=\"rounded-[14px] bg-card px-4 py-2.5 flex items-center gap-3\">\n <div className=\"h-8 w-8 rounded-full shrink-0 flex items-center justify-center bg-gradient-to-br from-brand-blue via-brand-purple to-brand-rose\">\n <Sparkles className=\"h-4 w-4 text-white\" />\n </div>\n <div className=\"min-w-0 leading-tight\">\n <p className=\"text-sm font-bold gradient-text-full truncate\">\n {score.levelName}\n </p>\n <p className=\"text-[11px] text-muted-foreground tabular-nums\">\n {score.totalXp.toLocaleString()} XP\n </p>\n </div>\n </div>\n </div>\n );\n\n return (\n <div\n className={cn(\"flex items-start justify-between gap-4\", className)}\n >\n <div className=\"space-y-1.5 min-w-0\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Briefcase className=\"h-4 w-4\" />\n <span className=\"text-xs font-semibold uppercase tracking-wider\">\n Portfolio\n </span>\n </div>\n <AddressDisplay address={address} chars={6} className=\"text-sm\" />\n </div>\n {score?.href ? (\n <Link href={score.href} className=\"shrink-0 active:opacity-80\">\n {scoreChip}\n </Link>\n ) : (\n <div className=\"shrink-0\">{scoreChip}</div>\n )}\n </div>\n );\n}\n"],"mappings":";AAoCU,cAMA,YANA;AAlCV,OAAO,UAAU;AACjB,SAAS,WAAW,gBAAgB;AACpC,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAsBxB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,YAAY,SAChB,oBAAC,SAAI,WAAU,6CACb,+BAAC,SAAI,WAAU,8DACb;AAAA,wBAAC,SAAI,WAAU,mIACb,8BAAC,YAAS,WAAU,sBAAqB,GAC3C;AAAA,IACA,qBAAC,SAAI,WAAU,yBACb;AAAA,0BAAC,OAAE,WAAU,iDACV,gBAAM,WACT;AAAA,MACA,qBAAC,OAAE,WAAU,kDACV;AAAA,cAAM,QAAQ,eAAe;AAAA,QAAE;AAAA,SAClC;AAAA,OACF;AAAA,KACF,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,0CAA0C,SAAS;AAAA,MAEjE;AAAA,6BAAC,SAAI,WAAU,uBACb;AAAA,+BAAC,SAAI,WAAU,wCACb;AAAA,gCAAC,aAAU,WAAU,WAAU;AAAA,YAC/B,oBAAC,UAAK,WAAU,kDAAiD,uBAEjE;AAAA,aACF;AAAA,UACA,oBAAC,kBAAe,SAAkB,OAAO,GAAG,WAAU,WAAU;AAAA,WAClE;AAAA,QACC,OAAO,OACN,oBAAC,QAAK,MAAM,MAAM,MAAM,WAAU,8BAC/B,qBACH,IAEA,oBAAC,SAAI,WAAU,YAAY,qBAAU;AAAA;AAAA;AAAA,EAEzC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/portfolio-header.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { Briefcase } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface PortfolioHeaderScore {\n /** Journey level name (\"Starter\", \"Explorer\", …) — no numeric level shown. */\n levelName: string;\n totalXp: number;\n /** Where the score chip links (\"/rewards\"). */\n href?: string;\n}\n\nexport interface PortfolioHeaderProps {\n address: string;\n score?: PortfolioHeaderScore | null;\n className?: string;\n}\n\n/**\n * Compact portfolio header: eyebrow + address on the left, the rewards\n * journey chip on the right — level name + XP inside the animated\n * full-spectrum brand border (`.btn-border-animated`). Counts live in the\n * Overview stat tiles, not here.\n */\nexport function PortfolioHeader({\n address,\n score,\n className,\n}: PortfolioHeaderProps) {\n const scoreChip = score && (\n <div className=\"btn-border-animated p-[1.5px] rounded-full\">\n <div className=\"rounded-full bg-card px-3.5 py-1.5 flex items-baseline gap-2 whitespace-nowrap\">\n <span className=\"text-sm font-semibold text-foreground\">\n {score.levelName}\n </span>\n <span className=\"text-xs text-muted-foreground tabular-nums\">\n {score.totalXp.toLocaleString()} XP\n </span>\n </div>\n </div>\n );\n\n return (\n <div\n className={cn(\"flex items-start justify-between gap-4\", className)}\n >\n <div className=\"space-y-1.5 min-w-0\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Briefcase className=\"h-4 w-4\" />\n <span className=\"text-xs font-semibold uppercase tracking-wider\">\n Portfolio\n </span>\n </div>\n <AddressDisplay address={address} chars={6} className=\"text-sm\" />\n </div>\n {score?.href ? (\n <Link href={score.href} className=\"shrink-0 active:opacity-80\">\n {scoreChip}\n </Link>\n ) : (\n <div className=\"shrink-0\">{scoreChip}</div>\n )}\n </div>\n );\n}\n"],"mappings":";AAmCQ,cAGA,YAHA;AAjCR,OAAO,UAAU;AACjB,SAAS,iBAAiB;AAC1B,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAsBxB,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,YAAY,SAChB,oBAAC,SAAI,WAAU,8CACb,+BAAC,SAAI,WAAU,kFACb;AAAA,wBAAC,UAAK,WAAU,yCACb,gBAAM,WACT;AAAA,IACA,qBAAC,UAAK,WAAU,8CACb;AAAA,YAAM,QAAQ,eAAe;AAAA,MAAE;AAAA,OAClC;AAAA,KACF,GACF;AAGF,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,0CAA0C,SAAS;AAAA,MAEjE;AAAA,6BAAC,SAAI,WAAU,uBACb;AAAA,+BAAC,SAAI,WAAU,wCACb;AAAA,gCAAC,aAAU,WAAU,WAAU;AAAA,YAC/B,oBAAC,UAAK,WAAU,kDAAiD,uBAEjE;AAAA,aACF;AAAA,UACA,oBAAC,kBAAe,SAAkB,OAAO,GAAG,WAAU,WAAU;AAAA,WAClE;AAAA,QACC,OAAO,OACN,oBAAC,QAAK,MAAM,MAAM,MAAM,WAAU,8BAC/B,qBACH,IAEA,oBAAC,SAAI,WAAU,YAAY,qBAAU;AAAA;AAAA;AAAA,EAEzC;AAEJ;","names":[]}
@@ -36,7 +36,6 @@ var import_jsx_runtime = require("react/jsx-runtime");
36
36
  var import_link = __toESM(require("next/link"), 1);
37
37
  var import_lucide_react = require("lucide-react");
38
38
  var import_cn = require("../utils/cn.js");
39
- var import_stat_tile = require("./stat-tile.js");
40
39
  const ATTENTION_DOT = {
41
40
  destructive: "bg-destructive",
42
41
  warning: "bg-amber-500",
@@ -107,40 +106,37 @@ function PortfolioOverview({
107
106
  item.href + item.label
108
107
  )) })
109
108
  ] }),
110
- stats && stats.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3", children: stats.map(
111
- (stat) => stat.href ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: stat.href, className: "active:opacity-80", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
112
- import_stat_tile.StatTile,
113
- {
114
- label: stat.label,
115
- value: stat.value ?? "\u2014",
116
- sub: stat.sub,
117
- big: true,
118
- className: "h-full"
109
+ (stats && stats.length > 0 || quickActions && quickActions.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-3", children: [
110
+ stats && stats.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2 flex-wrap", children: stats.map((stat) => {
111
+ if (stat.value == null) {
112
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ "span",
114
+ {
115
+ className: "bg-muted rounded-full px-3 py-1.5 w-20 h-7 animate-pulse inline-block"
116
+ },
117
+ stat.label
118
+ );
119
119
  }
120
- ) }, stat.label) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
121
- import_stat_tile.StatTile,
120
+ const pill = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-baseline gap-1.5 rounded-full bg-muted px-3 py-1.5 text-[13px] whitespace-nowrap", children: [
121
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold text-foreground tabular-nums", children: stat.value }),
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground", children: stat.label }),
123
+ stat.sub && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-muted-foreground/70", children: [
124
+ "\xB7 ",
125
+ stat.sub
126
+ ] })
127
+ ] });
128
+ return stat.href ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: stat.href, className: "active:opacity-80", children: pill }, stat.label) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: pill }, stat.label);
129
+ }) }),
130
+ quickActions && quickActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2 flex-wrap", children: quickActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ import_link.default,
122
132
  {
123
- label: stat.label,
124
- value: stat.value ?? "\u2014",
125
- sub: stat.sub,
126
- big: true,
127
- className: "h-full"
133
+ href: action.href,
134
+ className: "inline-flex items-center rounded-full border border-border bg-card px-4 py-1.5 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors",
135
+ children: action.label
128
136
  },
129
- stat.label
130
- )
131
- ) }),
132
- quickActions && quickActions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2 flex-wrap", children: quickActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
133
- import_link.default,
134
- {
135
- href: action.href,
136
- className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-4 py-2 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors",
137
- children: [
138
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Plus, { className: "h-3.5 w-3.5 text-primary" }),
139
- action.label
140
- ]
141
- },
142
- action.href
143
- )) }),
137
+ action.href
138
+ )) })
139
+ ] }),
144
140
  isEmpty ? emptyState : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid gap-6 lg:grid-cols-5", children: [
145
141
  assetsSlot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "lg:col-span-3 space-y-3", children: [
146
142
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SectionHeading, { title: "Your assets", href: assetsHref }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/portfolio-overview.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight, BellRing, Plus } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { StatTile } from \"./stat-tile.js\";\n\nexport interface PortfolioAttentionItem {\n /** e.g. \"2 offers received\" — caller composes count + label. */\n label: string;\n /** Short helper line, e.g. \"Accept, counter, or decline\". */\n description?: string;\n href: string;\n count: number;\n tone?: \"destructive\" | \"warning\" | \"primary\";\n}\n\nexport interface PortfolioOverviewStat {\n label: string;\n /** Pass null while loading. */\n value: string | number | null;\n sub?: string;\n href?: string;\n}\n\nexport interface PortfolioQuickAction {\n label: string;\n href: string;\n}\n\nexport interface PortfolioOverviewProps {\n /** Pending items needing action. The panel renders only when a count > 0. */\n attention?: PortfolioAttentionItem[];\n stats?: PortfolioOverviewStat[];\n /** Shortcut row under the stat tiles (\"Create asset\", …). */\n quickActions?: PortfolioQuickAction[];\n /** Recent-assets cards, rendered by the app. */\n assetsSlot?: React.ReactNode;\n assetsHref?: string;\n /** Recent-activity rows, rendered by the app. */\n activitySlot?: React.ReactNode;\n activityHref?: string;\n /** First-run state; rendered instead of the columns when `isEmpty`. */\n isEmpty?: boolean;\n emptyState?: React.ReactNode;\n className?: string;\n}\n\nconst ATTENTION_DOT: Record<\n NonNullable<PortfolioAttentionItem[\"tone\"]>,\n string\n> = {\n destructive: \"bg-destructive\",\n warning: \"bg-amber-500\",\n primary: \"bg-primary\",\n};\n\nfunction SectionHeading({\n title,\n href,\n}: {\n title: string;\n href?: string;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {href && (\n <Link\n href={href}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all\n <ArrowRight className=\"h-3 w-3\" />\n </Link>\n )}\n </div>\n );\n}\n\n/**\n * Portfolio landing page layout: needs-attention panel, clickable stat tiles,\n * and recent assets/activity columns. Pure presentation — the app injects\n * counts, stats, and rendered cards.\n */\nexport function PortfolioOverview({\n attention,\n stats,\n quickActions,\n assetsSlot,\n assetsHref,\n activitySlot,\n activityHref,\n isEmpty,\n emptyState,\n className,\n}: PortfolioOverviewProps) {\n const pending = (attention ?? []).filter((item) => item.count > 0);\n\n return (\n <div className={cn(\"space-y-6\", className)}>\n {pending.length > 0 && (\n <section className=\"rounded-xl border border-primary/25 bg-primary/[0.04] p-4 sm:p-5 space-y-3\">\n <div className=\"flex items-center gap-2 text-primary\">\n <BellRing className=\"h-4 w-4\" />\n <h2 className=\"text-sm font-semibold\">Needs your attention</h2>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {pending.map((item) => (\n <Link\n key={item.href + item.label}\n href={item.href}\n className=\"flex items-center justify-between gap-3 rounded-lg bg-card border border-border/60 px-3.5 py-2.5 active:opacity-80 hover:border-border transition-colors\"\n >\n <span className=\"flex items-center gap-2.5 min-w-0\">\n <span\n className={cn(\n \"h-2 w-2 rounded-full shrink-0\",\n ATTENTION_DOT[item.tone ?? \"primary\"],\n )}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium text-foreground truncate\">\n {item.label}\n </span>\n {item.description && (\n <span className=\"block text-xs text-muted-foreground truncate\">\n {item.description}\n </span>\n )}\n </span>\n </span>\n <ArrowRight className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n </Link>\n ))}\n </div>\n </section>\n )}\n\n {stats && stats.length > 0 && (\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3\">\n {stats.map((stat) =>\n stat.href ? (\n <Link key={stat.label} href={stat.href} className=\"active:opacity-80\">\n <StatTile\n label={stat.label}\n value={stat.value ?? \"—\"}\n sub={stat.sub}\n big\n className=\"h-full\"\n />\n </Link>\n ) : (\n <StatTile\n key={stat.label}\n label={stat.label}\n value={stat.value ?? \"—\"}\n sub={stat.sub}\n big\n className=\"h-full\"\n />\n ),\n )}\n </div>\n )}\n\n {quickActions && quickActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n {quickActions.map((action) => (\n <Link\n key={action.href}\n href={action.href}\n className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-4 py-2 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors\"\n >\n <Plus className=\"h-3.5 w-3.5 text-primary\" />\n {action.label}\n </Link>\n ))}\n </div>\n )}\n\n {isEmpty ? (\n emptyState\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-5\">\n {assetsSlot && (\n <section className=\"lg:col-span-3 space-y-3\">\n <SectionHeading title=\"Your assets\" href={assetsHref} />\n {assetsSlot}\n </section>\n )}\n {activitySlot && (\n <section className=\"lg:col-span-2 space-y-3\">\n <SectionHeading title=\"Recent activity\" href={activityHref} />\n {activitySlot}\n </section>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["Link"]}
1
+ {"version":3,"sources":["../../src/components/portfolio-overview.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight, BellRing } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport interface PortfolioAttentionItem {\n /** e.g. \"2 offers received\" — caller composes count + label. */\n label: string;\n /** Short helper line, e.g. \"Accept, counter, or decline\". */\n description?: string;\n href: string;\n count: number;\n tone?: \"destructive\" | \"warning\" | \"primary\";\n}\n\nexport interface PortfolioOverviewStat {\n label: string;\n /** Pass null while loading. */\n value: string | number | null;\n sub?: string;\n href?: string;\n}\n\nexport interface PortfolioQuickAction {\n label: string;\n href: string;\n}\n\nexport interface PortfolioOverviewProps {\n /** Pending items needing action. The panel renders only when a count > 0. */\n attention?: PortfolioAttentionItem[];\n stats?: PortfolioOverviewStat[];\n /** Shortcut row under the stat tiles (\"Create asset\", …). */\n quickActions?: PortfolioQuickAction[];\n /** Recent-assets cards, rendered by the app. */\n assetsSlot?: React.ReactNode;\n assetsHref?: string;\n /** Recent-activity rows, rendered by the app. */\n activitySlot?: React.ReactNode;\n activityHref?: string;\n /** First-run state; rendered instead of the columns when `isEmpty`. */\n isEmpty?: boolean;\n emptyState?: React.ReactNode;\n className?: string;\n}\n\nconst ATTENTION_DOT: Record<\n NonNullable<PortfolioAttentionItem[\"tone\"]>,\n string\n> = {\n destructive: \"bg-destructive\",\n warning: \"bg-amber-500\",\n primary: \"bg-primary\",\n};\n\nfunction SectionHeading({\n title,\n href,\n}: {\n title: string;\n href?: string;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-3\">\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {href && (\n <Link\n href={href}\n className=\"inline-flex items-center gap-1 text-xs font-medium text-muted-foreground hover:text-foreground transition-colors\"\n >\n View all\n <ArrowRight className=\"h-3 w-3\" />\n </Link>\n )}\n </div>\n );\n}\n\n/**\n * Portfolio landing page layout: needs-attention panel, clickable stat tiles,\n * and recent assets/activity columns. Pure presentation — the app injects\n * counts, stats, and rendered cards.\n */\nexport function PortfolioOverview({\n attention,\n stats,\n quickActions,\n assetsSlot,\n assetsHref,\n activitySlot,\n activityHref,\n isEmpty,\n emptyState,\n className,\n}: PortfolioOverviewProps) {\n const pending = (attention ?? []).filter((item) => item.count > 0);\n\n return (\n <div className={cn(\"space-y-6\", className)}>\n {pending.length > 0 && (\n <section className=\"rounded-xl border border-primary/25 bg-primary/[0.04] p-4 sm:p-5 space-y-3\">\n <div className=\"flex items-center gap-2 text-primary\">\n <BellRing className=\"h-4 w-4\" />\n <h2 className=\"text-sm font-semibold\">Needs your attention</h2>\n </div>\n <div className=\"grid gap-2 sm:grid-cols-2\">\n {pending.map((item) => (\n <Link\n key={item.href + item.label}\n href={item.href}\n className=\"flex items-center justify-between gap-3 rounded-lg bg-card border border-border/60 px-3.5 py-2.5 active:opacity-80 hover:border-border transition-colors\"\n >\n <span className=\"flex items-center gap-2.5 min-w-0\">\n <span\n className={cn(\n \"h-2 w-2 rounded-full shrink-0\",\n ATTENTION_DOT[item.tone ?? \"primary\"],\n )}\n />\n <span className=\"min-w-0\">\n <span className=\"block text-sm font-medium text-foreground truncate\">\n {item.label}\n </span>\n {item.description && (\n <span className=\"block text-xs text-muted-foreground truncate\">\n {item.description}\n </span>\n )}\n </span>\n </span>\n <ArrowRight className=\"h-4 w-4 text-muted-foreground shrink-0\" />\n </Link>\n ))}\n </div>\n </section>\n )}\n\n {((stats && stats.length > 0) || (quickActions && quickActions.length > 0)) && (\n <div className=\"space-y-3\">\n {stats && stats.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n {stats.map((stat) => {\n if (stat.value == null) {\n return (\n <span\n key={stat.label}\n className=\"bg-muted rounded-full px-3 py-1.5 w-20 h-7 animate-pulse inline-block\"\n />\n );\n }\n const pill = (\n <span className=\"inline-flex items-baseline gap-1.5 rounded-full bg-muted px-3 py-1.5 text-[13px] whitespace-nowrap\">\n <span className=\"font-semibold text-foreground tabular-nums\">\n {stat.value}\n </span>\n <span className=\"text-muted-foreground\">{stat.label}</span>\n {stat.sub && (\n <span className=\"text-muted-foreground/70\">\n · {stat.sub}\n </span>\n )}\n </span>\n );\n return stat.href ? (\n <Link key={stat.label} href={stat.href} className=\"active:opacity-80\">\n {pill}\n </Link>\n ) : (\n <span key={stat.label}>{pill}</span>\n );\n })}\n </div>\n )}\n\n {quickActions && quickActions.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n {quickActions.map((action) => (\n <Link\n key={action.href}\n href={action.href}\n className=\"inline-flex items-center rounded-full border border-border bg-card px-4 py-1.5 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors\"\n >\n {action.label}\n </Link>\n ))}\n </div>\n )}\n </div>\n )}\n\n {isEmpty ? (\n emptyState\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-5\">\n {assetsSlot && (\n <section className=\"lg:col-span-3 space-y-3\">\n <SectionHeading title=\"Your assets\" href={assetsHref} />\n {assetsSlot}\n </section>\n )}\n {activitySlot && (\n <section className=\"lg:col-span-2 space-y-3\">\n <SectionHeading title=\"Recent activity\" href={activityHref} />\n {activitySlot}\n </section>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["Link"]}
@@ -1,9 +1,8 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
- import { ArrowRight, BellRing, Plus } from "lucide-react";
4
+ import { ArrowRight, BellRing } from "lucide-react";
5
5
  import { cn } from "../utils/cn.js";
6
- import { StatTile } from "./stat-tile.js";
7
6
  const ATTENTION_DOT = {
8
7
  destructive: "bg-destructive",
9
8
  warning: "bg-amber-500",
@@ -74,40 +73,37 @@ function PortfolioOverview({
74
73
  item.href + item.label
75
74
  )) })
76
75
  ] }),
77
- stats && stats.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3", children: stats.map(
78
- (stat) => stat.href ? /* @__PURE__ */ jsx(Link, { href: stat.href, className: "active:opacity-80", children: /* @__PURE__ */ jsx(
79
- StatTile,
80
- {
81
- label: stat.label,
82
- value: stat.value ?? "\u2014",
83
- sub: stat.sub,
84
- big: true,
85
- className: "h-full"
76
+ (stats && stats.length > 0 || quickActions && quickActions.length > 0) && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
77
+ stats && stats.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children: stats.map((stat) => {
78
+ if (stat.value == null) {
79
+ return /* @__PURE__ */ jsx(
80
+ "span",
81
+ {
82
+ className: "bg-muted rounded-full px-3 py-1.5 w-20 h-7 animate-pulse inline-block"
83
+ },
84
+ stat.label
85
+ );
86
86
  }
87
- ) }, stat.label) : /* @__PURE__ */ jsx(
88
- StatTile,
87
+ const pill = /* @__PURE__ */ jsxs("span", { className: "inline-flex items-baseline gap-1.5 rounded-full bg-muted px-3 py-1.5 text-[13px] whitespace-nowrap", children: [
88
+ /* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground tabular-nums", children: stat.value }),
89
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: stat.label }),
90
+ stat.sub && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70", children: [
91
+ "\xB7 ",
92
+ stat.sub
93
+ ] })
94
+ ] });
95
+ return stat.href ? /* @__PURE__ */ jsx(Link, { href: stat.href, className: "active:opacity-80", children: pill }, stat.label) : /* @__PURE__ */ jsx("span", { children: pill }, stat.label);
96
+ }) }),
97
+ quickActions && quickActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children: quickActions.map((action) => /* @__PURE__ */ jsx(
98
+ Link,
89
99
  {
90
- label: stat.label,
91
- value: stat.value ?? "\u2014",
92
- sub: stat.sub,
93
- big: true,
94
- className: "h-full"
100
+ href: action.href,
101
+ className: "inline-flex items-center rounded-full border border-border bg-card px-4 py-1.5 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors",
102
+ children: action.label
95
103
  },
96
- stat.label
97
- )
98
- ) }),
99
- quickActions && quickActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-wrap", children: quickActions.map((action) => /* @__PURE__ */ jsxs(
100
- Link,
101
- {
102
- href: action.href,
103
- className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-4 py-2 text-[13px] font-medium text-foreground active:opacity-80 hover:border-primary/40 transition-colors",
104
- children: [
105
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5 text-primary" }),
106
- action.label
107
- ]
108
- },
109
- action.href
110
- )) }),
104
+ action.href
105
+ )) })
106
+ ] }),
111
107
  isEmpty ? emptyState : /* @__PURE__ */ jsxs("div", { className: "grid gap-6 lg:grid-cols-5", children: [
112
108
  assetsSlot && /* @__PURE__ */ jsxs("section", { className: "lg:col-span-3 space-y-3", children: [
113
109
  /* @__PURE__ */ jsx(SectionHeading, { title: "Your assets", href: assetsHref }),