@medialane/ui 0.125.7 → 0.126.1

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 (32) hide show
  1. package/dist/components/asset-top-sections.cjs +53 -24
  2. package/dist/components/asset-top-sections.cjs.map +1 -1
  3. package/dist/components/asset-top-sections.js +53 -24
  4. package/dist/components/asset-top-sections.js.map +1 -1
  5. package/dist/components/{portfolio-overview.cjs → portfolio-section-grid.cjs} +19 -24
  6. package/dist/components/portfolio-section-grid.cjs.map +1 -0
  7. package/dist/components/portfolio-section-grid.d.cts +21 -0
  8. package/dist/components/portfolio-section-grid.d.ts +21 -0
  9. package/dist/components/portfolio-section-grid.js +41 -0
  10. package/dist/components/portfolio-section-grid.js.map +1 -0
  11. package/dist/components/{portfolio-bento-tile.cjs → portfolio-section.cjs} +21 -12
  12. package/dist/components/portfolio-section.cjs.map +1 -0
  13. package/dist/components/portfolio-section.d.cts +18 -0
  14. package/dist/components/portfolio-section.d.ts +18 -0
  15. package/dist/components/{portfolio-bento-tile.js → portfolio-section.js} +17 -8
  16. package/dist/components/portfolio-section.js.map +1 -0
  17. package/dist/index.cjs +6 -6
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +2 -2
  20. package/dist/index.d.ts +2 -2
  21. package/dist/index.js +4 -4
  22. package/dist/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/dist/components/portfolio-bento-tile.cjs.map +0 -1
  25. package/dist/components/portfolio-bento-tile.d.cts +0 -17
  26. package/dist/components/portfolio-bento-tile.d.ts +0 -17
  27. package/dist/components/portfolio-bento-tile.js.map +0 -1
  28. package/dist/components/portfolio-overview.cjs.map +0 -1
  29. package/dist/components/portfolio-overview.d.cts +0 -20
  30. package/dist/components/portfolio-overview.d.ts +0 -20
  31. package/dist/components/portfolio-overview.js +0 -44
  32. package/dist/components/portfolio-overview.js.map +0 -1
@@ -37,8 +37,10 @@ __export(asset_top_sections_exports, {
37
37
  module.exports = __toCommonJS(asset_top_sections_exports);
38
38
  var import_jsx_runtime = require("react/jsx-runtime");
39
39
  var import_link = __toESM(require("next/link"), 1);
40
+ var import_react = require("react");
40
41
  var import_framer_motion = require("framer-motion");
41
42
  var import_use_intersection_active = require("../utils/use-intersection-active.js");
43
+ var import_cn = require("../utils/cn.js");
42
44
  var import_address_display = require("./address-display.js");
43
45
  var import_parent_attribution_banner = require("./parent-attribution-banner.js");
44
46
  var import_ip_type_badge = require("./ip-type-badge.js");
@@ -56,7 +58,19 @@ function AssetMediaColumn({
56
58
  stats
57
59
  }) {
58
60
  const [ref, isVisible] = (0, import_use_intersection_active.useIntersectionActive)();
61
+ const [loaded, setLoaded] = (0, import_react.useState)(false);
62
+ (0, import_react.useEffect)(() => setLoaded(false), [image]);
59
63
  const showLive = live && !!animationUrl && isVisible;
64
+ const shimmer = !loaded && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
65
+ "div",
66
+ {
67
+ className: "absolute inset-0 min-h-80 animate-[shimmer_1.6s_ease-in-out_infinite] bg-foreground/[0.06]",
68
+ style: {
69
+ backgroundImage: "linear-gradient(90deg, transparent, color-mix(in srgb, var(--foreground) 8%, transparent), transparent)",
70
+ backgroundSize: "200% 100%"
71
+ }
72
+ }
73
+ );
60
74
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { ref, className: "w-full lg:sticky lg:top-16", children: [
61
75
  showLive ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full overflow-hidden rounded-3xl aspect-square", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
62
76
  "iframe",
@@ -67,7 +81,7 @@ function AssetMediaColumn({
67
81
  loading: "lazy",
68
82
  className: "w-full h-full border-0"
69
83
  }
70
- ) }) : !image || imgError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
84
+ ) }) : !image || imgError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
71
85
  import_framer_motion.motion.button,
72
86
  {
73
87
  type: "button",
@@ -76,35 +90,50 @@ function AssetMediaColumn({
76
90
  initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
77
91
  animate: { opacity: 1, scale: 1 },
78
92
  transition: { duration: 0.5 },
79
- className: "group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none",
80
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
81
- "img",
82
- {
83
- src: image,
84
- alt: imageAlt,
85
- crossOrigin: "anonymous",
86
- onError: onImageError,
87
- className: "w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]"
88
- }
89
- )
93
+ className: "group relative block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none",
94
+ children: [
95
+ shimmer,
96
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
97
+ "img",
98
+ {
99
+ src: image,
100
+ alt: imageAlt,
101
+ crossOrigin: "anonymous",
102
+ onLoad: () => setLoaded(true),
103
+ onError: onImageError,
104
+ className: (0, import_cn.cn)(
105
+ "w-full h-auto max-h-[80vh] object-contain",
106
+ "transition-opacity duration-300 group-hover:opacity-95 group-active:scale-[0.99]",
107
+ loaded ? "opacity-100" : "opacity-0"
108
+ )
109
+ }
110
+ )
111
+ ]
90
112
  }
91
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
113
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
92
114
  import_framer_motion.motion.div,
93
115
  {
94
116
  initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
95
117
  animate: { opacity: 1, scale: 1 },
96
118
  transition: { duration: 0.5 },
97
- className: "block w-full overflow-hidden rounded-3xl",
98
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
99
- "img",
100
- {
101
- src: image,
102
- alt: imageAlt,
103
- crossOrigin: "anonymous",
104
- onError: onImageError,
105
- className: "w-full h-auto max-h-[80vh] object-contain"
106
- }
107
- )
119
+ className: "relative block w-full overflow-hidden rounded-3xl",
120
+ children: [
121
+ shimmer,
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
123
+ "img",
124
+ {
125
+ src: image,
126
+ alt: imageAlt,
127
+ crossOrigin: "anonymous",
128
+ onLoad: () => setLoaded(true),
129
+ onError: onImageError,
130
+ className: (0, import_cn.cn)(
131
+ "w-full h-auto max-h-[80vh] object-contain transition-opacity duration-300",
132
+ loaded ? "opacity-100" : "opacity-0"
133
+ )
134
+ }
135
+ )
136
+ ]
108
137
  }
109
138
  ),
110
139
  stats && stats.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `grid gap-3 mt-4 ${stats.length === 2 ? "grid-cols-2" : "grid-cols-1"}`, children: stats.map((stat) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "rounded-xl border border-border bg-muted/20 p-4 text-center", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n\n onZoom?: () => void;\n\n animationUrl?: string | null;\n\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const showLive = live && !!animationUrl && isVisible;\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]\"\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"block w-full overflow-hidden rounded-3xl\"\n >\n\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\"\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div className=\"min-w-0 flex-1\">\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-2xs font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-2xl lg:text-4xl font-bold break-words\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":["Link"]}
1
+ {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { useEffect, useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n\n onZoom?: () => void;\n\n animationUrl?: string | null;\n\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const [loaded, setLoaded] = useState(false);\n useEffect(() => setLoaded(false), [image]);\n const showLive = live && !!animationUrl && isVisible;\n\n const shimmer = !loaded && (\n <div\n className=\"absolute inset-0 min-h-80 animate-[shimmer_1.6s_ease-in-out_infinite] bg-foreground/[0.06]\"\n style={{\n backgroundImage:\n \"linear-gradient(90deg, transparent, color-mix(in srgb, var(--foreground) 8%, transparent), transparent)\",\n backgroundSize: \"200% 100%\",\n }}\n />\n );\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group relative block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n {shimmer}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onLoad={() => setLoaded(true)}\n onError={onImageError}\n className={cn(\n \"w-full h-auto max-h-[80vh] object-contain\",\n \"transition-opacity duration-300 group-hover:opacity-95 group-active:scale-[0.99]\",\n loaded ? \"opacity-100\" : \"opacity-0\"\n )}\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"relative block w-full overflow-hidden rounded-3xl\"\n >\n {shimmer}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onLoad={() => setLoaded(true)}\n onError={onImageError}\n className={cn(\n \"w-full h-auto max-h-[80vh] object-contain transition-opacity duration-300\",\n loaded ? \"opacity-100\" : \"opacity-0\"\n )}\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div className=\"min-w-0 flex-1\">\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-2xs font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-2xl lg:text-4xl font-bold break-words\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":["Link"]}
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
+ import { useEffect, useState } from "react";
4
5
  import { motion } from "framer-motion";
5
6
  import { useIntersectionActive } from "../utils/use-intersection-active.js";
7
+ import { cn } from "../utils/cn.js";
6
8
  import { AddressDisplay } from "./address-display.js";
7
9
  import { ParentAttributionBanner } from "./parent-attribution-banner.js";
8
10
  import { IpTypeBadge } from "./ip-type-badge.js";
@@ -20,7 +22,19 @@ function AssetMediaColumn({
20
22
  stats
21
23
  }) {
22
24
  const [ref, isVisible] = useIntersectionActive();
25
+ const [loaded, setLoaded] = useState(false);
26
+ useEffect(() => setLoaded(false), [image]);
23
27
  const showLive = live && !!animationUrl && isVisible;
28
+ const shimmer = !loaded && /* @__PURE__ */ jsx(
29
+ "div",
30
+ {
31
+ className: "absolute inset-0 min-h-80 animate-[shimmer_1.6s_ease-in-out_infinite] bg-foreground/[0.06]",
32
+ style: {
33
+ backgroundImage: "linear-gradient(90deg, transparent, color-mix(in srgb, var(--foreground) 8%, transparent), transparent)",
34
+ backgroundSize: "200% 100%"
35
+ }
36
+ }
37
+ );
24
38
  return /* @__PURE__ */ jsxs("div", { ref, className: "w-full lg:sticky lg:top-16", children: [
25
39
  showLive ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl aspect-square", children: /* @__PURE__ */ jsx(
26
40
  "iframe",
@@ -31,7 +45,7 @@ function AssetMediaColumn({
31
45
  loading: "lazy",
32
46
  className: "w-full h-full border-0"
33
47
  }
34
- ) }) : !image || imgError ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ jsx(
48
+ ) }) : !image || imgError ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ jsxs(
35
49
  motion.button,
36
50
  {
37
51
  type: "button",
@@ -40,35 +54,50 @@ function AssetMediaColumn({
40
54
  initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
41
55
  animate: { opacity: 1, scale: 1 },
42
56
  transition: { duration: 0.5 },
43
- className: "group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none",
44
- children: /* @__PURE__ */ jsx(
45
- "img",
46
- {
47
- src: image,
48
- alt: imageAlt,
49
- crossOrigin: "anonymous",
50
- onError: onImageError,
51
- className: "w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]"
52
- }
53
- )
57
+ className: "group relative block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none",
58
+ children: [
59
+ shimmer,
60
+ /* @__PURE__ */ jsx(
61
+ "img",
62
+ {
63
+ src: image,
64
+ alt: imageAlt,
65
+ crossOrigin: "anonymous",
66
+ onLoad: () => setLoaded(true),
67
+ onError: onImageError,
68
+ className: cn(
69
+ "w-full h-auto max-h-[80vh] object-contain",
70
+ "transition-opacity duration-300 group-hover:opacity-95 group-active:scale-[0.99]",
71
+ loaded ? "opacity-100" : "opacity-0"
72
+ )
73
+ }
74
+ )
75
+ ]
54
76
  }
55
- ) : /* @__PURE__ */ jsx(
77
+ ) : /* @__PURE__ */ jsxs(
56
78
  motion.div,
57
79
  {
58
80
  initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
59
81
  animate: { opacity: 1, scale: 1 },
60
82
  transition: { duration: 0.5 },
61
- className: "block w-full overflow-hidden rounded-3xl",
62
- children: /* @__PURE__ */ jsx(
63
- "img",
64
- {
65
- src: image,
66
- alt: imageAlt,
67
- crossOrigin: "anonymous",
68
- onError: onImageError,
69
- className: "w-full h-auto max-h-[80vh] object-contain"
70
- }
71
- )
83
+ className: "relative block w-full overflow-hidden rounded-3xl",
84
+ children: [
85
+ shimmer,
86
+ /* @__PURE__ */ jsx(
87
+ "img",
88
+ {
89
+ src: image,
90
+ alt: imageAlt,
91
+ crossOrigin: "anonymous",
92
+ onLoad: () => setLoaded(true),
93
+ onError: onImageError,
94
+ className: cn(
95
+ "w-full h-auto max-h-[80vh] object-contain transition-opacity duration-300",
96
+ loaded ? "opacity-100" : "opacity-0"
97
+ )
98
+ }
99
+ )
100
+ ]
72
101
  }
73
102
  ),
74
103
  stats && stats.length > 0 ? /* @__PURE__ */ jsx("div", { className: `grid gap-3 mt-4 ${stats.length === 2 ? "grid-cols-2" : "grid-cols-1"}`, children: stats.map((stat) => /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-muted/20 p-4 text-center", children: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n\n onZoom?: () => void;\n\n animationUrl?: string | null;\n\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const showLive = live && !!animationUrl && isVisible;\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\n transition duration-300 group-hover:opacity-95 group-active:scale-[0.99]\"\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"block w-full overflow-hidden rounded-3xl\"\n >\n\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onError={onImageError}\n className=\"w-full h-auto max-h-[80vh] object-contain\"\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div className=\"min-w-0 flex-1\">\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-2xs font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-2xl lg:text-4xl font-bold break-words\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { useEffect, useState } from \"react\";\nimport { motion } from \"framer-motion\";\nimport { useIntersectionActive } from \"../utils/use-intersection-active.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { ParentAttributionBanner } from \"./parent-attribution-banner.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { Layers, Users } from \"lucide-react\";\n\ninterface AssetMediaColumnProps {\n shouldReduce: boolean;\n image: string | null;\n imageAlt: string;\n imgError: boolean;\n onImageError: () => void;\n fallback: React.ReactNode;\n\n onZoom?: () => void;\n\n animationUrl?: string | null;\n\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\nexport function AssetMediaColumn({\n shouldReduce,\n image,\n imageAlt,\n imgError,\n onImageError,\n fallback,\n onZoom,\n animationUrl,\n live = false,\n stats,\n}: AssetMediaColumnProps) {\n const [ref, isVisible] = useIntersectionActive<HTMLDivElement>();\n const [loaded, setLoaded] = useState(false);\n useEffect(() => setLoaded(false), [image]);\n const showLive = live && !!animationUrl && isVisible;\n\n const shimmer = !loaded && (\n <div\n className=\"absolute inset-0 min-h-80 animate-[shimmer_1.6s_ease-in-out_infinite] bg-foreground/[0.06]\"\n style={{\n backgroundImage:\n \"linear-gradient(90deg, transparent, color-mix(in srgb, var(--foreground) 8%, transparent), transparent)\",\n backgroundSize: \"200% 100%\",\n }}\n />\n );\n\n return (\n <div ref={ref} className=\"w-full lg:sticky lg:top-16\">\n {showLive ? (\n <div className=\"w-full overflow-hidden rounded-3xl aspect-square\">\n <iframe\n src={animationUrl!}\n title={imageAlt}\n sandbox=\"allow-scripts\"\n loading=\"lazy\"\n className=\"w-full h-full border-0\"\n />\n </div>\n ) : !image || imgError ? (\n <div className=\"w-full overflow-hidden rounded-3xl\">{fallback}</div>\n ) : onZoom ? (\n <motion.button\n type=\"button\"\n onClick={onZoom}\n aria-label=\"View full image\"\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"group relative block w-full overflow-hidden rounded-3xl cursor-zoom-in focus:outline-none\"\n >\n {shimmer}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onLoad={() => setLoaded(true)}\n onError={onImageError}\n className={cn(\n \"w-full h-auto max-h-[80vh] object-contain\",\n \"transition-opacity duration-300 group-hover:opacity-95 group-active:scale-[0.99]\",\n loaded ? \"opacity-100\" : \"opacity-0\"\n )}\n />\n </motion.button>\n ) : (\n <motion.div\n initial={shouldReduce ? false : { opacity: 0, scale: 0.98 }}\n animate={{ opacity: 1, scale: 1 }}\n transition={{ duration: 0.5 }}\n className=\"relative block w-full overflow-hidden rounded-3xl\"\n >\n {shimmer}\n <img\n src={image}\n alt={imageAlt}\n crossOrigin=\"anonymous\"\n onLoad={() => setLoaded(true)}\n onError={onImageError}\n className={cn(\n \"w-full h-auto max-h-[80vh] object-contain transition-opacity duration-300\",\n loaded ? \"opacity-100\" : \"opacity-0\"\n )}\n />\n </motion.div>\n )}\n\n {stats && stats.length > 0 ? (\n <div className={`grid gap-3 mt-4 ${stats.length === 2 ? \"grid-cols-2\" : \"grid-cols-1\"}`}>\n {stats.map((stat) => (\n <div key={stat.label} className=\"rounded-xl border border-border bg-muted/20 p-4 text-center\">\n <p className=\"text-2xl font-black\">{stat.value}</p>\n <div className=\"flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1\">\n {stat.icon}\n {stat.label}\n </div>\n </div>\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface AssetHeaderBlockProps {\n name: string;\n description?: string | null;\n ipType?: string | null;\n showMultiEditionBadge?: boolean;\n parentContract?: string | null;\n parentTokenId?: string | null;\n}\n\nexport function AssetHeaderBlock({\n name,\n description,\n ipType,\n showMultiEditionBadge = false,\n parentContract,\n parentTokenId,\n}: AssetHeaderBlockProps) {\n return (\n <div className=\"min-w-0 flex-1\">\n {ipType || showMultiEditionBadge ? (\n <div className=\"flex items-center gap-2 flex-wrap mb-2\">\n {ipType ? <IpTypeBadge ipType={ipType} size=\"md\" /> : null}\n {showMultiEditionBadge ? (\n <span className=\"inline-flex items-center gap-1 text-2xs font-semibold px-2.5 py-1 rounded-full border border-violet-500/30 bg-violet-500/10 text-violet-500\">\n <Layers className=\"h-3 w-3\" />\n Multi-edition\n </span>\n ) : null}\n </div>\n ) : null}\n {parentContract && parentTokenId ? (\n <div className=\"mb-3\">\n <ParentAttributionBanner\n parentContract={parentContract}\n parentTokenId={parentTokenId}\n parentName={`Token #${parentTokenId}`}\n />\n </div>\n ) : null}\n <h1 className=\"text-2xl lg:text-4xl font-bold break-words\">{name}</h1>\n {description ? (\n <p className=\"text-sm text-muted-foreground leading-relaxed mt-1\">{description}</p>\n ) : null}\n </div>\n );\n}\n\nexport interface AssetOwnerRowProps {\n ownerAddress: string;\n ownerHref: string;\n}\n\nexport function AssetOwnerRow({ ownerAddress, ownerHref }: AssetOwnerRowProps) {\n return (\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <span>Owner</span>\n <Link href={ownerHref} className=\"hover:text-primary transition-colors font-medium\">\n <AddressDisplay address={ownerAddress} />\n </Link>\n </div>\n );\n}\n\nexport function buildEditionStats(totalEditions: number, uniqueOwners: number) {\n return [\n {\n value: totalEditions.toLocaleString(),\n label: \"editions minted\",\n icon: <Layers className=\"h-3 w-3\" />,\n },\n {\n value: uniqueOwners.toLocaleString(),\n label: \"unique owners\",\n icon: <Users className=\"h-3 w-3\" />,\n },\n ];\n}\n"],"mappings":"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","names":[]}
@@ -17,16 +17,16 @@ var __copyProps = (to, from, except, desc) => {
17
17
  return to;
18
18
  };
19
19
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
- var portfolio_overview_exports = {};
21
- __export(portfolio_overview_exports, {
22
- PortfolioOverview: () => PortfolioOverview
20
+ var portfolio_section_grid_exports = {};
21
+ __export(portfolio_section_grid_exports, {
22
+ PortfolioSectionGrid: () => PortfolioSectionGrid
23
23
  });
24
- module.exports = __toCommonJS(portfolio_overview_exports);
24
+ module.exports = __toCommonJS(portfolio_section_grid_exports);
25
25
  var import_jsx_runtime = require("react/jsx-runtime");
26
26
  var import_cn = require("../utils/cn.js");
27
- var import_portfolio_bento_tile = require("./portfolio-bento-tile.js");
28
- function PortfolioOverview({
29
- tiles,
27
+ var import_portfolio_section = require("./portfolio-section.js");
28
+ function PortfolioSectionGrid({
29
+ sections,
30
30
  isEmpty,
31
31
  emptyState,
32
32
  className
@@ -34,35 +34,30 @@ function PortfolioOverview({
34
34
  if (isEmpty) {
35
35
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className, children: emptyState });
36
36
  }
37
- const populatedTiles = tiles.filter((t) => !t.isEmpty);
37
+ const populated = sections.filter((s) => !s.isEmpty);
38
38
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
39
39
  "div",
40
40
  {
41
41
  className: (0, import_cn.cn)(
42
- // True masonry: a CSS multi-column container. Each tile keeps its
43
- // own natural height and flows into whichever column is currently
44
- // shortest — unlike a grid, nothing stretches to match a taller
45
- // sibling in the same row, which is what real (unevenly-sized)
46
- // portfolio content needs. "wide" tiles opt out via column-span:
47
- // all (set on PortfolioBentoTile itself, not here).
48
- "columns-1 sm:columns-2 lg:columns-3 gap-4",
42
+ "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 items-start",
49
43
  className
50
44
  ),
51
- children: populatedTiles.map((tile) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
52
- import_portfolio_bento_tile.PortfolioBentoTile,
45
+ children: populated.map((section) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
46
+ import_portfolio_section.PortfolioSection,
53
47
  {
54
- title: tile.title,
55
- href: tile.href,
56
- size: tile.size,
57
- children: tile.content
48
+ title: section.title,
49
+ href: section.href,
50
+ color: section.color,
51
+ size: section.size,
52
+ children: section.content
58
53
  },
59
- tile.key
54
+ section.key
60
55
  ))
61
56
  }
62
57
  );
63
58
  }
64
59
  // Annotate the CommonJS export names for ESM import in node:
65
60
  0 && (module.exports = {
66
- PortfolioOverview
61
+ PortfolioSectionGrid
67
62
  });
68
- //# sourceMappingURL=portfolio-overview.cjs.map
63
+ //# sourceMappingURL=portfolio-section-grid.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/portfolio-section-grid.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn } from \"../utils/cn.js\";\nimport {\n PortfolioSection,\n type PortfolioSectionProps,\n} from \"./portfolio-section.js\";\n\nexport interface PortfolioSectionConfig {\n key: string;\n title: string;\n href: string;\n color?: PortfolioSectionProps[\"color\"];\n size?: PortfolioSectionProps[\"size\"];\n\n isEmpty?: boolean;\n content: React.ReactNode;\n}\n\nexport interface PortfolioSectionGridProps {\n sections: PortfolioSectionConfig[];\n\n isEmpty?: boolean;\n emptyState?: React.ReactNode;\n className?: string;\n}\n\nexport function PortfolioSectionGrid({\n sections,\n isEmpty,\n emptyState,\n className,\n}: PortfolioSectionGridProps) {\n if (isEmpty) {\n return <div className={className}>{emptyState}</div>;\n }\n\n const populated = sections.filter((s) => !s.isEmpty);\n\n return (\n <div\n className={cn(\n \"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 items-start\",\n className,\n )}\n >\n {populated.map((section) => (\n <PortfolioSection\n key={section.key}\n title={section.title}\n href={section.href}\n color={section.color}\n size={section.size}\n >\n {section.content}\n </PortfolioSection>\n ))}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAkCW;AAhCX,gBAAmB;AACnB,+BAGO;AAqBA,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA8B;AAC5B,MAAI,SAAS;AACX,WAAO,4CAAC,SAAI,WAAuB,sBAAW;AAAA,EAChD;AAEA,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AAEnD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEC,oBAAU,IAAI,CAAC,YACd;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ;AAAA,UACd,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ;AAAA,UAEb,kBAAQ;AAAA;AAAA,QANJ,QAAQ;AAAA,MAOf,CACD;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -0,0 +1,21 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PortfolioSectionProps } from './portfolio-section.cjs';
3
+
4
+ interface PortfolioSectionConfig {
5
+ key: string;
6
+ title: string;
7
+ href: string;
8
+ color?: PortfolioSectionProps["color"];
9
+ size?: PortfolioSectionProps["size"];
10
+ isEmpty?: boolean;
11
+ content: React.ReactNode;
12
+ }
13
+ interface PortfolioSectionGridProps {
14
+ sections: PortfolioSectionConfig[];
15
+ isEmpty?: boolean;
16
+ emptyState?: React.ReactNode;
17
+ className?: string;
18
+ }
19
+ declare function PortfolioSectionGrid({ sections, isEmpty, emptyState, className, }: PortfolioSectionGridProps): react_jsx_runtime.JSX.Element;
20
+
21
+ export { type PortfolioSectionConfig, PortfolioSectionGrid, type PortfolioSectionGridProps };
@@ -0,0 +1,21 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { PortfolioSectionProps } from './portfolio-section.js';
3
+
4
+ interface PortfolioSectionConfig {
5
+ key: string;
6
+ title: string;
7
+ href: string;
8
+ color?: PortfolioSectionProps["color"];
9
+ size?: PortfolioSectionProps["size"];
10
+ isEmpty?: boolean;
11
+ content: React.ReactNode;
12
+ }
13
+ interface PortfolioSectionGridProps {
14
+ sections: PortfolioSectionConfig[];
15
+ isEmpty?: boolean;
16
+ emptyState?: React.ReactNode;
17
+ className?: string;
18
+ }
19
+ declare function PortfolioSectionGrid({ sections, isEmpty, emptyState, className, }: PortfolioSectionGridProps): react_jsx_runtime.JSX.Element;
20
+
21
+ export { type PortfolioSectionConfig, PortfolioSectionGrid, type PortfolioSectionGridProps };
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { cn } from "../utils/cn.js";
4
+ import {
5
+ PortfolioSection
6
+ } from "./portfolio-section.js";
7
+ function PortfolioSectionGrid({
8
+ sections,
9
+ isEmpty,
10
+ emptyState,
11
+ className
12
+ }) {
13
+ if (isEmpty) {
14
+ return /* @__PURE__ */ jsx("div", { className, children: emptyState });
15
+ }
16
+ const populated = sections.filter((s) => !s.isEmpty);
17
+ return /* @__PURE__ */ jsx(
18
+ "div",
19
+ {
20
+ className: cn(
21
+ "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 items-start",
22
+ className
23
+ ),
24
+ children: populated.map((section) => /* @__PURE__ */ jsx(
25
+ PortfolioSection,
26
+ {
27
+ title: section.title,
28
+ href: section.href,
29
+ color: section.color,
30
+ size: section.size,
31
+ children: section.content
32
+ },
33
+ section.key
34
+ ))
35
+ }
36
+ );
37
+ }
38
+ export {
39
+ PortfolioSectionGrid
40
+ };
41
+ //# sourceMappingURL=portfolio-section-grid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/portfolio-section-grid.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn } from \"../utils/cn.js\";\nimport {\n PortfolioSection,\n type PortfolioSectionProps,\n} from \"./portfolio-section.js\";\n\nexport interface PortfolioSectionConfig {\n key: string;\n title: string;\n href: string;\n color?: PortfolioSectionProps[\"color\"];\n size?: PortfolioSectionProps[\"size\"];\n\n isEmpty?: boolean;\n content: React.ReactNode;\n}\n\nexport interface PortfolioSectionGridProps {\n sections: PortfolioSectionConfig[];\n\n isEmpty?: boolean;\n emptyState?: React.ReactNode;\n className?: string;\n}\n\nexport function PortfolioSectionGrid({\n sections,\n isEmpty,\n emptyState,\n className,\n}: PortfolioSectionGridProps) {\n if (isEmpty) {\n return <div className={className}>{emptyState}</div>;\n }\n\n const populated = sections.filter((s) => !s.isEmpty);\n\n return (\n <div\n className={cn(\n \"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 items-start\",\n className,\n )}\n >\n {populated.map((section) => (\n <PortfolioSection\n key={section.key}\n title={section.title}\n href={section.href}\n color={section.color}\n size={section.size}\n >\n {section.content}\n </PortfolioSection>\n ))}\n </div>\n );\n}\n"],"mappings":";AAkCW;AAhCX,SAAS,UAAU;AACnB;AAAA,EACE;AAAA,OAEK;AAqBA,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA8B;AAC5B,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAuB,sBAAW;AAAA,EAChD;AAEA,QAAM,YAAY,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO;AAEnD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEC,oBAAU,IAAI,CAAC,YACd;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ;AAAA,UACd,OAAO,QAAQ;AAAA,UACf,MAAM,QAAQ;AAAA,UAEb,kBAAQ;AAAA;AAAA,QANJ,QAAQ;AAAA,MAOf,CACD;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -27,18 +27,27 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  mod
28
28
  ));
29
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
- var portfolio_bento_tile_exports = {};
31
- __export(portfolio_bento_tile_exports, {
32
- PortfolioBentoTile: () => PortfolioBentoTile
30
+ var portfolio_section_exports = {};
31
+ __export(portfolio_section_exports, {
32
+ PortfolioSection: () => PortfolioSection
33
33
  });
34
- module.exports = __toCommonJS(portfolio_bento_tile_exports);
34
+ module.exports = __toCommonJS(portfolio_section_exports);
35
35
  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
- function PortfolioBentoTile({
39
+ const BORDER_BY_COLOR = {
40
+ blue: "border-brand-blue/40",
41
+ purple: "border-brand-purple/40",
42
+ rose: "border-brand-rose/40",
43
+ orange: "border-brand-orange/40",
44
+ maeve: "border-brand-maeve/40",
45
+ navy: "border-brand-navy/40"
46
+ };
47
+ function PortfolioSection({
40
48
  title,
41
49
  href,
50
+ color = "purple",
42
51
  size = "default",
43
52
  children,
44
53
  className
@@ -47,13 +56,13 @@ function PortfolioBentoTile({
47
56
  "section",
48
57
  {
49
58
  className: (0, import_cn.cn)(
50
- "rounded-2xl bg-foreground/[0.04] min-w-0 transition-colors hover:bg-foreground/[0.07]",
51
- "mb-4 break-inside-avoid",
52
- size === "wide" && "[column-span:all]",
59
+ "rounded-2xl border min-w-0",
60
+ BORDER_BY_COLOR[color],
61
+ size === "wide" && "sm:col-span-2 lg:col-span-3",
53
62
  className
54
63
  ),
55
64
  children: [
56
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3 px-4 pt-4 pb-3", children: [
65
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3 px-5 pt-5 pb-3", children: [
57
66
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-sm font-black text-foreground tracking-tight", children: title }),
58
67
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
59
68
  import_link.default,
@@ -67,13 +76,13 @@ function PortfolioBentoTile({
67
76
  }
68
77
  )
69
78
  ] }),
70
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-4 pb-4", children })
79
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "px-5 pb-5", children })
71
80
  ]
72
81
  }
73
82
  );
74
83
  }
75
84
  // Annotate the CommonJS export names for ESM import in node:
76
85
  0 && (module.exports = {
77
- PortfolioBentoTile
86
+ PortfolioSection
78
87
  });
79
- //# sourceMappingURL=portfolio-bento-tile.cjs.map
88
+ //# sourceMappingURL=portfolio-section.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/portfolio-section.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\n\nexport type PortfolioSectionColor =\n | \"blue\"\n | \"purple\"\n | \"rose\"\n | \"orange\"\n | \"maeve\"\n | \"navy\";\n\nconst BORDER_BY_COLOR: Record<PortfolioSectionColor, string> = {\n blue: \"border-brand-blue/40\",\n purple: \"border-brand-purple/40\",\n rose: \"border-brand-rose/40\",\n orange: \"border-brand-orange/40\",\n maeve: \"border-brand-maeve/40\",\n navy: \"border-brand-navy/40\",\n};\n\nexport interface PortfolioSectionProps {\n title: string;\n href: string;\n\n /** Which brand color accents this section's hairline border. */\n color?: PortfolioSectionColor;\n\n /** \"default\" sits in the grid alongside its siblings; \"wide\" spans the\n * full row instead — for list-like content (e.g. Activity) that reads\n * better edge-to-edge than column-width. */\n size?: \"default\" | \"wide\";\n children: React.ReactNode;\n className?: string;\n}\n\nexport function PortfolioSection({\n title,\n href,\n color = \"purple\",\n size = \"default\",\n children,\n className,\n}: PortfolioSectionProps) {\n return (\n <section\n className={cn(\n \"rounded-2xl border min-w-0\",\n BORDER_BY_COLOR[color],\n size === \"wide\" && \"sm:col-span-2 lg:col-span-3\",\n className,\n )}\n >\n <div className=\"flex items-center justify-between gap-3 px-5 pt-5 pb-3\">\n <h2 className=\"text-sm font-black text-foreground tracking-tight\">\n {title}\n </h2>\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 See all\n <ArrowRight className=\"h-3 w-3\" />\n </Link>\n </div>\n <div className=\"px-5 pb-5\">{children}</div>\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwDQ;AAtDR,kBAAiB;AACjB,0BAA2B;AAC3B,gBAAmB;AAUnB,MAAM,kBAAyD;AAAA,EAC7D,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,MAAM;AACR;AAiBO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,OAAO;AAAA,EACP;AAAA,EACA;AACF,GAA0B;AACxB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,QACT;AAAA,QACA,gBAAgB,KAAK;AAAA,QACrB,SAAS,UAAU;AAAA,QACnB;AAAA,MACF;AAAA,MAEA;AAAA,qDAAC,SAAI,WAAU,0DACb;AAAA,sDAAC,QAAG,WAAU,qDACX,iBACH;AAAA,UACA;AAAA,YAAC,YAAAA;AAAA,YAAA;AAAA,cACC;AAAA,cACA,WAAU;AAAA,cACX;AAAA;AAAA,gBAEC,4CAAC,kCAAW,WAAU,WAAU;AAAA;AAAA;AAAA,UAClC;AAAA,WACF;AAAA,QACA,4CAAC,SAAI,WAAU,aAAa,UAAS;AAAA;AAAA;AAAA,EACvC;AAEJ;","names":["Link"]}
@@ -0,0 +1,18 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type PortfolioSectionColor = "blue" | "purple" | "rose" | "orange" | "maeve" | "navy";
4
+ interface PortfolioSectionProps {
5
+ title: string;
6
+ href: string;
7
+ /** Which brand color accents this section's hairline border. */
8
+ color?: PortfolioSectionColor;
9
+ /** "default" sits in the grid alongside its siblings; "wide" spans the
10
+ * full row instead — for list-like content (e.g. Activity) that reads
11
+ * better edge-to-edge than column-width. */
12
+ size?: "default" | "wide";
13
+ children: React.ReactNode;
14
+ className?: string;
15
+ }
16
+ declare function PortfolioSection({ title, href, color, size, children, className, }: PortfolioSectionProps): react_jsx_runtime.JSX.Element;
17
+
18
+ export { PortfolioSection, type PortfolioSectionColor, type PortfolioSectionProps };
@@ -0,0 +1,18 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ type PortfolioSectionColor = "blue" | "purple" | "rose" | "orange" | "maeve" | "navy";
4
+ interface PortfolioSectionProps {
5
+ title: string;
6
+ href: string;
7
+ /** Which brand color accents this section's hairline border. */
8
+ color?: PortfolioSectionColor;
9
+ /** "default" sits in the grid alongside its siblings; "wide" spans the
10
+ * full row instead — for list-like content (e.g. Activity) that reads
11
+ * better edge-to-edge than column-width. */
12
+ size?: "default" | "wide";
13
+ children: React.ReactNode;
14
+ className?: string;
15
+ }
16
+ declare function PortfolioSection({ title, href, color, size, children, className, }: PortfolioSectionProps): react_jsx_runtime.JSX.Element;
17
+
18
+ export { PortfolioSection, type PortfolioSectionColor, type PortfolioSectionProps };