@medialane/ui 0.83.0 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/animated-token-media.cjs +100 -0
- package/dist/components/animated-token-media.cjs.map +1 -0
- package/dist/components/animated-token-media.d.cts +28 -0
- package/dist/components/animated-token-media.d.ts +28 -0
- package/dist/components/animated-token-media.js +66 -0
- package/dist/components/animated-token-media.js.map +1 -0
- package/dist/components/asset-card.cjs +14 -13
- package/dist/components/asset-card.cjs.map +1 -1
- package/dist/components/asset-card.d.cts +5 -1
- package/dist/components/asset-card.d.ts +5 -1
- package/dist/components/asset-card.js +14 -13
- package/dist/components/asset-card.js.map +1 -1
- package/dist/components/asset-top-sections.cjs +58 -27
- package/dist/components/asset-top-sections.cjs.map +1 -1
- package/dist/components/asset-top-sections.d.cts +19 -2
- package/dist/components/asset-top-sections.d.ts +19 -2
- package/dist/components/asset-top-sections.js +58 -27
- package/dist/components/asset-top-sections.js.map +1 -1
- package/dist/components/token-card.cjs +15 -13
- package/dist/components/token-card.cjs.map +1 -1
- package/dist/components/token-card.js +15 -13
- package/dist/components/token-card.js.map +1 -1
- package/dist/data/living-render-collections.cjs +43 -0
- package/dist/data/living-render-collections.cjs.map +1 -0
- package/dist/data/living-render-collections.d.cts +16 -0
- package/dist/data/living-render-collections.d.ts +16 -0
- package/dist/data/living-render-collections.js +18 -0
- package/dist/data/living-render-collections.js.map +1 -0
- package/dist/index.cjs +11 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/use-intersection-active.cjs +49 -0
- package/dist/utils/use-intersection-active.cjs.map +1 -0
- package/dist/utils/use-intersection-active.d.cts +12 -0
- package/dist/utils/use-intersection-active.d.ts +12 -0
- package/dist/utils/use-intersection-active.js +25 -0
- package/dist/utils/use-intersection-active.js.map +1 -0
- package/package.json +4 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport
|
|
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 /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */\n onZoom?: () => void;\n /** Resolved animation_url — the live on-chain renderer, if any. */\n animationUrl?: string | null;\n /** Caller-computed eligibility for the living-render treatment. */\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\n/**\n * The platform's one asset media column — borderless, respects the work's\n * real aspect ratio (never forced 1:1), capped to the viewport so it always\n * fits on screen, optional click-to-zoom (foundations §III: image leads, no\n * border clutter).\n *\n * `live`-eligible tokens (a small partner allowlist, see\n * living-render-collections) swap the static image for a sandboxed iframe of\n * the token's own on-chain animation_url once the media scrolls into view —\n * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.\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 {/* eslint-disable-next-line @next/next/no-img-element */}\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 {/* eslint-disable-next-line @next/next/no-img-element */}\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>\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-[11px] 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-3xl lg:text-5xl font-bold\">{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\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4DU;AA1DV,kBAAiB;AACjB,2BAAuB;AACvB,qCAAsC;AACtC,6BAA+B;AAC/B,uCAAwC;AACxC,2BAA4B;AAC5B,0BAA8B;AAiCvB,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAA0B;AACxB,QAAM,CAAC,KAAK,SAAS,QAAI,sDAAsC;AAC/D,QAAM,WAAW,QAAQ,CAAC,CAAC,gBAAgB;AAE3C,SACE,6CAAC,SAAI,KAAU,WAAU,8BACtB;AAAA,eACC,4CAAC,SAAI,WAAU,oDACb;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL,OAAO;AAAA,QACP,SAAQ;AAAA,QACR,SAAQ;AAAA,QACR,WAAU;AAAA;AAAA,IACZ,GACF,IACE,CAAC,SAAS,WACZ,4CAAC,SAAI,WAAU,sCAAsC,oBAAS,IAC5D,SACF;AAAA,MAAC,4BAAO;AAAA,MAAP;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,cAAW;AAAA,QACX,SAAS,eAAe,QAAQ,EAAE,SAAS,GAAG,OAAO,KAAK;AAAA,QAC1D,SAAS,EAAE,SAAS,GAAG,OAAO,EAAE;AAAA,QAChC,YAAY,EAAE,UAAU,IAAI;AAAA,QAC5B,WAAU;AAAA,QAGV;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK;AAAA,YACL,aAAY;AAAA,YACZ,SAAS;AAAA,YACT,WAAU;AAAA;AAAA,QAEZ;AAAA;AAAA,IACF,IAEA;AAAA,MAAC,4BAAO;AAAA,MAAP;AAAA,QACC,SAAS,eAAe,QAAQ,EAAE,SAAS,GAAG,OAAO,KAAK;AAAA,QAC1D,SAAS,EAAE,SAAS,GAAG,OAAO,EAAE;AAAA,QAChC,YAAY,EAAE,UAAU,IAAI;AAAA,QAC5B,WAAU;AAAA,QAGV;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,KAAK;AAAA,YACL,aAAY;AAAA,YACZ,SAAS;AAAA,YACT,WAAU;AAAA;AAAA,QACZ;AAAA;AAAA,IACF;AAAA,IAGD,SAAS,MAAM,SAAS,IACvB,4CAAC,SAAI,WAAW,mBAAmB,MAAM,WAAW,IAAI,gBAAgB,aAAa,IAClF,gBAAM,IAAI,CAAC,SACV,6CAAC,SAAqB,WAAU,+DAC9B;AAAA,kDAAC,OAAE,WAAU,uBAAuB,eAAK,OAAM;AAAA,MAC/C,6CAAC,SAAI,WAAU,6EACZ;AAAA,aAAK;AAAA,QACL,KAAK;AAAA,SACR;AAAA,SALQ,KAAK,KAMf,CACD,GACH,IACE;AAAA,KACN;AAEJ;AAWO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAwB;AAAA,EACxB;AAAA,EACA;AACF,GAA0B;AACxB,SACE,6CAAC,SACE;AAAA,cAAU,wBACT,6CAAC,SAAI,WAAU,0CACZ;AAAA,eAAS,4CAAC,oCAAY,QAAgB,MAAK,MAAK,IAAK;AAAA,MACrD,wBACC,6CAAC,UAAK,WAAU,kJACd;AAAA,oDAAC,8BAAO,WAAU,WAAU;AAAA,QAAE;AAAA,SAEhC,IACE;AAAA,OACN,IACE;AAAA,IACH,kBAAkB,gBACjB,4CAAC,SAAI,WAAU,QACb;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,YAAY,UAAU,aAAa;AAAA;AAAA,IACrC,GACF,IACE;AAAA,IACJ,4CAAC,QAAG,WAAU,kCAAkC,gBAAK;AAAA,IACpD,cACC,4CAAC,OAAE,WAAU,sDAAsD,uBAAY,IAC7E;AAAA,KACN;AAEJ;AAWO,SAAS,cAAc,EAAE,cAAc,UAAU,GAAuB;AAC7E,SACE,6CAAC,SAAI,WAAU,2DACb;AAAA,gDAAC,UAAK,mBAAK;AAAA,IACX,4CAAC,YAAAA,SAAA,EAAK,MAAM,WAAW,WAAU,oDAC/B,sDAAC,yCAAe,SAAS,cAAc,GACzC;AAAA,KACF;AAEJ;AAEO,SAAS,kBAAkB,eAAuB,cAAsB;AAC7E,SAAO;AAAA,IACL;AAAA,MACE,OAAO,cAAc,eAAe;AAAA,MACpC,OAAO;AAAA,MACP,MAAM,4CAAC,8BAAO,WAAU,WAAU;AAAA,IACpC;AAAA,IACA;AAAA,MACE,OAAO,aAAa,eAAe;AAAA,MACnC,OAAO;AAAA,MACP,MAAM,4CAAC,6BAAM,WAAU,WAAU;AAAA,IACnC;AAAA,EACF;AACF;","names":["Link"]}
|
|
@@ -2,18 +2,35 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
interface AssetMediaColumnProps {
|
|
4
4
|
shouldReduce: boolean;
|
|
5
|
-
image: string;
|
|
5
|
+
image: string | null;
|
|
6
6
|
imageAlt: string;
|
|
7
7
|
imgError: boolean;
|
|
8
8
|
onImageError: () => void;
|
|
9
9
|
fallback: React.ReactNode;
|
|
10
|
+
/** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */
|
|
11
|
+
onZoom?: () => void;
|
|
12
|
+
/** Resolved animation_url — the live on-chain renderer, if any. */
|
|
13
|
+
animationUrl?: string | null;
|
|
14
|
+
/** Caller-computed eligibility for the living-render treatment. */
|
|
15
|
+
live?: boolean;
|
|
10
16
|
stats?: Array<{
|
|
11
17
|
value: string;
|
|
12
18
|
label: string;
|
|
13
19
|
icon: React.ReactNode;
|
|
14
20
|
}>;
|
|
15
21
|
}
|
|
16
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The platform's one asset media column — borderless, respects the work's
|
|
24
|
+
* real aspect ratio (never forced 1:1), capped to the viewport so it always
|
|
25
|
+
* fits on screen, optional click-to-zoom (foundations §III: image leads, no
|
|
26
|
+
* border clutter).
|
|
27
|
+
*
|
|
28
|
+
* `live`-eligible tokens (a small partner allowlist, see
|
|
29
|
+
* living-render-collections) swap the static image for a sandboxed iframe of
|
|
30
|
+
* the token's own on-chain animation_url once the media scrolls into view —
|
|
31
|
+
* see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.
|
|
32
|
+
*/
|
|
33
|
+
declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, onZoom, animationUrl, live, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
|
|
17
34
|
interface AssetHeaderBlockProps {
|
|
18
35
|
name: string;
|
|
19
36
|
description?: string | null;
|
|
@@ -2,18 +2,35 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
|
2
2
|
|
|
3
3
|
interface AssetMediaColumnProps {
|
|
4
4
|
shouldReduce: boolean;
|
|
5
|
-
image: string;
|
|
5
|
+
image: string | null;
|
|
6
6
|
imageAlt: string;
|
|
7
7
|
imgError: boolean;
|
|
8
8
|
onImageError: () => void;
|
|
9
9
|
fallback: React.ReactNode;
|
|
10
|
+
/** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */
|
|
11
|
+
onZoom?: () => void;
|
|
12
|
+
/** Resolved animation_url — the live on-chain renderer, if any. */
|
|
13
|
+
animationUrl?: string | null;
|
|
14
|
+
/** Caller-computed eligibility for the living-render treatment. */
|
|
15
|
+
live?: boolean;
|
|
10
16
|
stats?: Array<{
|
|
11
17
|
value: string;
|
|
12
18
|
label: string;
|
|
13
19
|
icon: React.ReactNode;
|
|
14
20
|
}>;
|
|
15
21
|
}
|
|
16
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The platform's one asset media column — borderless, respects the work's
|
|
24
|
+
* real aspect ratio (never forced 1:1), capped to the viewport so it always
|
|
25
|
+
* fits on screen, optional click-to-zoom (foundations §III: image leads, no
|
|
26
|
+
* border clutter).
|
|
27
|
+
*
|
|
28
|
+
* `live`-eligible tokens (a small partner allowlist, see
|
|
29
|
+
* living-render-collections) swap the static image for a sandboxed iframe of
|
|
30
|
+
* the token's own on-chain animation_url once the media scrolls into view —
|
|
31
|
+
* see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.
|
|
32
|
+
*/
|
|
33
|
+
declare function AssetMediaColumn({ shouldReduce, image, imageAlt, imgError, onImageError, fallback, onZoom, animationUrl, live, stats, }: AssetMediaColumnProps): react_jsx_runtime.JSX.Element;
|
|
17
34
|
interface AssetHeaderBlockProps {
|
|
18
35
|
name: string;
|
|
19
36
|
description?: string | null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import Image from "next/image";
|
|
4
3
|
import Link from "next/link";
|
|
5
4
|
import { motion } from "framer-motion";
|
|
5
|
+
import { useIntersectionActive } from "../utils/use-intersection-active.js";
|
|
6
6
|
import { AddressDisplay } from "./address-display.js";
|
|
7
7
|
import { ParentAttributionBanner } from "./parent-attribution-banner.js";
|
|
8
8
|
import { IpTypeBadge } from "./ip-type-badge.js";
|
|
@@ -14,40 +14,71 @@ function AssetMediaColumn({
|
|
|
14
14
|
imgError,
|
|
15
15
|
onImageError,
|
|
16
16
|
fallback,
|
|
17
|
+
onZoom,
|
|
18
|
+
animationUrl,
|
|
19
|
+
live = false,
|
|
17
20
|
stats
|
|
18
21
|
}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const [ref, isVisible] = useIntersectionActive();
|
|
23
|
+
const showLive = live && !!animationUrl && isVisible;
|
|
24
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "w-full lg:sticky lg:top-16", children: [
|
|
25
|
+
showLive ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl aspect-square", children: /* @__PURE__ */ jsx(
|
|
26
|
+
"iframe",
|
|
27
|
+
{
|
|
28
|
+
src: animationUrl,
|
|
29
|
+
title: imageAlt,
|
|
30
|
+
sandbox: "allow-scripts",
|
|
31
|
+
loading: "lazy",
|
|
32
|
+
className: "w-full h-full border-0"
|
|
33
|
+
}
|
|
34
|
+
) }) : !image || imgError ? /* @__PURE__ */ jsx("div", { className: "w-full overflow-hidden rounded-3xl", children: fallback }) : onZoom ? /* @__PURE__ */ jsx(
|
|
35
|
+
motion.button,
|
|
36
|
+
{
|
|
37
|
+
type: "button",
|
|
38
|
+
onClick: onZoom,
|
|
39
|
+
"aria-label": "View full image",
|
|
40
|
+
initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
|
|
41
|
+
animate: { opacity: 1, scale: 1 },
|
|
42
|
+
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",
|
|
29
46
|
{
|
|
30
47
|
src: image,
|
|
31
48
|
alt: imageAlt,
|
|
32
|
-
|
|
33
|
-
height: 0,
|
|
34
|
-
sizes: "(max-width: 1024px) 100vw, 66vw",
|
|
35
|
-
className: "w-full h-auto",
|
|
49
|
+
crossOrigin: "anonymous",
|
|
36
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
|
+
)
|
|
54
|
+
}
|
|
55
|
+
) : /* @__PURE__ */ jsx(
|
|
56
|
+
motion.div,
|
|
57
|
+
{
|
|
58
|
+
initial: shouldReduce ? false : { opacity: 0, scale: 0.98 },
|
|
59
|
+
animate: { opacity: 1, scale: 1 },
|
|
60
|
+
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,
|
|
37
67
|
crossOrigin: "anonymous",
|
|
38
|
-
|
|
68
|
+
onError: onImageError,
|
|
69
|
+
className: "w-full h-auto max-h-[80vh] object-contain"
|
|
39
70
|
}
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
);
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
),
|
|
74
|
+
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: [
|
|
75
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-black", children: stat.value }),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1 text-xs text-muted-foreground mt-1", children: [
|
|
77
|
+
stat.icon,
|
|
78
|
+
stat.label
|
|
79
|
+
] })
|
|
80
|
+
] }, stat.label)) }) : null
|
|
81
|
+
] });
|
|
51
82
|
}
|
|
52
83
|
function AssetHeaderBlock({
|
|
53
84
|
name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/asset-top-sections.tsx"],"sourcesContent":["\"use client\";\n\nimport
|
|
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 /** Opens the full-screen lightbox. Omit for a non-interactive (still borderless, real-aspect-ratio) render. */\n onZoom?: () => void;\n /** Resolved animation_url — the live on-chain renderer, if any. */\n animationUrl?: string | null;\n /** Caller-computed eligibility for the living-render treatment. */\n live?: boolean;\n stats?: Array<{\n value: string;\n label: string;\n icon: React.ReactNode;\n }>;\n}\n\n/**\n * The platform's one asset media column — borderless, respects the work's\n * real aspect ratio (never forced 1:1), capped to the viewport so it always\n * fits on screen, optional click-to-zoom (foundations §III: image leads, no\n * border clutter).\n *\n * `live`-eligible tokens (a small partner allowlist, see\n * living-render-collections) swap the static image for a sandboxed iframe of\n * the token's own on-chain animation_url once the media scrolls into view —\n * see medialane-core/docs/specs/2026-07-28-gol-starknet-living-render-design.md.\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 {/* eslint-disable-next-line @next/next/no-img-element */}\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 {/* eslint-disable-next-line @next/next/no-img-element */}\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>\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-[11px] 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-3xl lg:text-5xl font-bold\">{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\n/** Single-owner identity (ERC-721) — its own sibling row, not nested inside\n * the collection bar (that reads as if ownership were a collection\n * attribute, 2026-07-05 feedback). ERC-1155 editions use `AssetOwnersPanel`\n * instead (multiple owners). */\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":[]}
|
|
@@ -34,9 +34,7 @@ __export(token_card_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(token_card_exports);
|
|
36
36
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
|
-
var import_react = require("react");
|
|
38
37
|
var import_link = __toESM(require("next/link"), 1);
|
|
39
|
-
var import_image = __toESM(require("next/image"), 1);
|
|
40
38
|
var import_lucide_react = require("lucide-react");
|
|
41
39
|
var import_dropdown_menu = require("./dropdown-menu.js");
|
|
42
40
|
var import_cn = require("../utils/cn.js");
|
|
@@ -44,6 +42,8 @@ var import_ipfs = require("../utils/ipfs.js");
|
|
|
44
42
|
var import_format = require("../utils/format.js");
|
|
45
43
|
var import_currency_icon = require("./currency-icon.js");
|
|
46
44
|
var import_ip_type_badge = require("./ip-type-badge.js");
|
|
45
|
+
var import_animated_token_media = require("./animated-token-media.js");
|
|
46
|
+
var import_living_render_collections = require("../data/living-render-collections.js");
|
|
47
47
|
var import_sdk = require("@medialane/sdk");
|
|
48
48
|
const BTN_BASE = "h-8 rounded-[9px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0";
|
|
49
49
|
const BTN_OUTLINE = (0, import_cn.cn)(BTN_BASE, "border border-border/60 text-foreground hover:bg-muted/60");
|
|
@@ -57,9 +57,9 @@ function TokenCard({
|
|
|
57
57
|
onOffer,
|
|
58
58
|
onReport
|
|
59
59
|
}) {
|
|
60
|
-
const [imgError, setImgError] = (0, import_react.useState)(false);
|
|
61
60
|
const name = token.metadata?.name || `Token #${token.tokenId}`;
|
|
62
61
|
const image = (0, import_ipfs.ipfsToHttp)(token.metadata?.image);
|
|
62
|
+
const animationUrl = token.metadata?.animationUrl ?? null;
|
|
63
63
|
const ipType = token.metadata?.ipType;
|
|
64
64
|
const listingOrder = token.activeOrders?.find(
|
|
65
65
|
(o) => o.offer.itemType === "ERC721" || o.offer.itemType === "ERC1155"
|
|
@@ -68,6 +68,7 @@ function TokenCard({
|
|
|
68
68
|
const creator = typeof creatorAttr?.value === "string" ? creatorAttr.value : null;
|
|
69
69
|
const owner = token.balances?.[0]?.owner ?? token.owner ?? null;
|
|
70
70
|
const chain = token.chain.toUpperCase();
|
|
71
|
+
const live = (0, import_living_render_collections.isLivingRenderCollection)(chain, token.contractAddress);
|
|
71
72
|
const assetHref = (0, import_sdk.assetHref)(chain, token.contractAddress, token.tokenId);
|
|
72
73
|
const collectionHref = (0, import_sdk.collectionHref)(chain, token.contractAddress);
|
|
73
74
|
const remixHref = `/create/remix/${token.contractAddress}/${token.tokenId}`;
|
|
@@ -84,21 +85,22 @@ function TokenCard({
|
|
|
84
85
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-base relative overflow-hidden flex flex-col w-full", children: [
|
|
85
86
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href: assetHref, className: "flex flex-col flex-1", children: [
|
|
86
87
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-square bg-muted overflow-hidden shrink-0", children: [
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
89
|
+
import_animated_token_media.AnimatedTokenMedia,
|
|
89
90
|
{
|
|
90
|
-
|
|
91
|
+
image,
|
|
92
|
+
animationUrl,
|
|
93
|
+
live,
|
|
91
94
|
alt: name,
|
|
92
|
-
|
|
93
|
-
unoptimized: true,
|
|
95
|
+
mode: "fill",
|
|
94
96
|
sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw",
|
|
95
97
|
className: "object-cover",
|
|
96
|
-
|
|
98
|
+
fallback: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xl tabular-nums text-muted-foreground", children: [
|
|
99
|
+
"#",
|
|
100
|
+
token.tokenId
|
|
101
|
+
] }) })
|
|
97
102
|
}
|
|
98
|
-
)
|
|
99
|
-
"#",
|
|
100
|
-
token.tokenId
|
|
101
|
-
] }) }),
|
|
103
|
+
),
|
|
102
104
|
listingOrder && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute bottom-2 right-2 z-10", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-bold bg-background/90 backdrop-blur-sm border border-border/40 shadow-sm", children: [
|
|
103
105
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: listingOrder.price.currency ?? "", size: 10 }),
|
|
104
106
|
(0, import_format.formatDisplayPrice)(listingOrder.price.formatted),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/token-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport {\n Loader2, ArrowUpRight, MoreHorizontal, Zap, HandCoins, Tag,\n Layers, GitBranch, Flag, UserCircle2, ArrowRightLeft, XCircle,\n} from \"lucide-react\";\nimport {\n DropdownMenu, DropdownMenuContent, DropdownMenuItem,\n DropdownMenuSeparator, DropdownMenuTrigger,\n} from \"./dropdown-menu.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { assetHref as buildAssetHref, collectionHref as buildCollectionHref } from \"@medialane/sdk\";\nimport type { ApiToken, Chain } from \"@medialane/sdk\";\n\nconst BTN_BASE = \"h-8 rounded-[9px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0\";\nconst BTN_OUTLINE = cn(BTN_BASE, \"border border-border/60 text-foreground hover:bg-muted/60\");\n\nexport interface TokenCardProps {\n token: ApiToken;\n isOwner?: boolean;\n onList?: (token: ApiToken) => void;\n onTransfer?: (token: ApiToken) => void;\n onCancel?: (token: ApiToken) => void;\n onBuy?: (token: ApiToken) => void;\n onOffer?: (token: ApiToken) => void;\n onReport?: (token: ApiToken) => void;\n}\n\nexport function TokenCard({\n token,\n isOwner = false,\n onList,\n onTransfer,\n onCancel,\n onBuy,\n onOffer,\n onReport,\n}: TokenCardProps) {\n const [imgError, setImgError] = useState(false);\n\n const name = token.metadata?.name || `Token #${token.tokenId}`;\n const image = ipfsToHttp(token.metadata?.image);\n const ipType = token.metadata?.ipType;\n\n // First listing-type active order (offer.itemType = ERC721 or ERC1155)\n const listingOrder = token.activeOrders?.find(\n (o) => o.offer.itemType === \"ERC721\" || o.offer.itemType === \"ERC1155\"\n );\n\n const creatorAttr = token.metadata?.attributes?.find((a) => a.trait_type === \"Creator\");\n const creator = typeof creatorAttr?.value === \"string\" ? creatorAttr.value : null;\n const owner = token.balances?.[0]?.owner ?? token.owner ?? null;\n\n const chain = token.chain.toUpperCase() as Chain;\n const assetHref = buildAssetHref(chain, token.contractAddress, token.tokenId);\n const collectionHref = buildCollectionHref(chain, token.contractAddress);\n const remixHref = `/create/remix/${token.contractAddress}/${token.tokenId}`;\n\n const handleBuy = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onBuy?.(token);\n };\n\n const handleOffer = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onOffer?.(token);\n };\n\n return (\n <div className=\"card-base relative overflow-hidden flex flex-col w-full\">\n\n {/* ── Clickable image + info ─────────────────────────────── */}\n <Link href={assetHref} className=\"flex flex-col flex-1\">\n\n {/* Image */}\n <div className=\"relative aspect-square bg-muted overflow-hidden shrink-0\">\n {!imgError ? (\n <Image\n src={image}\n alt={name}\n fill\n unoptimized\n sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw\"\n className=\"object-cover\"\n onError={() => setImgError(true)}\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15\">\n <span className=\"text-2xl tabular-nums text-muted-foreground\">#{token.tokenId}</span>\n </div>\n )}\n\n {/* Price chip — bottom right overlay */}\n {listingOrder && (\n <div className=\"absolute bottom-2 right-2 z-10\">\n <span className=\"inline-flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-bold bg-background/90 backdrop-blur-sm border border-border/40 shadow-sm\">\n <CurrencyIcon symbol={listingOrder.price.currency ?? \"\"} size={10} />\n {formatDisplayPrice(listingOrder.price.formatted)}\n <span className=\"text-muted-foreground font-normal\">{listingOrder.price.currency}</span>\n </span>\n </div>\n )}\n\n {/* Indexing indicator */}\n {(token.metadataStatus === \"PENDING\" || token.metadataStatus === \"FETCHING\") && (\n <div className=\"absolute bottom-0 inset-x-0 flex items-center justify-center gap-1.5 bg-black/50 backdrop-blur-sm py-1.5\">\n <Loader2 className=\"h-3 w-3 animate-spin text-white/70\" />\n <span className=\"text-[10px] text-white/70\">Indexing…</span>\n </div>\n )}\n </div>\n\n {/* Info */}\n <div className=\"px-3 pt-2.5 pb-4 flex-1\">\n <p className=\"text-base font-bold line-clamp-2 leading-snug\">{name}</p>\n <div className=\"flex items-center gap-1.5 mt-1.5 flex-wrap\">\n {ipType && <IpTypeBadge ipType={ipType as any} size=\"sm\" />}\n {creator ? (\n <p className=\"text-[10px] tabular-nums text-muted-foreground/60 truncate\">\n {creator.slice(0, 8)}…{creator.slice(-6)}\n </p>\n ) : token.metadata?.description ? (\n <p className=\"text-[10px] text-muted-foreground truncate leading-snug\">\n {token.metadata.description}\n </p>\n ) : null}\n </div>\n </div>\n </Link>\n\n {/* ── Action row ────────────────────────────────────────── */}\n <div className=\"px-3 pb-3 flex items-center gap-1.5\">\n\n {/* Expand */}\n <Link\n href={assetHref}\n className={cn(BTN_OUTLINE, \"w-8 shrink-0 px-0\")}\n title=\"View asset\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5\" />\n </Link>\n\n {/* Owner: List (or Listed label) — Buyer: Buy / Make offer */}\n {isOwner ? (\n listingOrder ? (\n <Link href={assetHref} className={cn(BTN_OUTLINE, \"flex-1\")}>\n <ArrowUpRight className=\"h-3.5 w-3.5 mr-1.5\" />\n View\n </Link>\n ) : (\n <button\n className={cn(BTN_OUTLINE, \"flex-1 text-brand-purple border-brand-purple/30 hover:bg-brand-purple/10 hover:text-brand-purple\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onList?.(token); }}\n >\n <Tag className=\"h-3.5 w-3.5 mr-1.5\" />\n List\n </button>\n )\n ) : listingOrder ? (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-8\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={handleBuy}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" />\n Buy\n </button>\n </div>\n ) : (\n <button\n className={cn(BTN_OUTLINE, \"flex-1 text-brand-orange border-brand-orange/30 hover:bg-brand-orange/10 hover:text-brand-orange\")}\n onClick={handleOffer}\n >\n <HandCoins className=\"h-3.5 w-3.5 mr-1.5\" />\n Offer\n </button>\n )}\n\n {/* 3-dot menu */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n className={cn(BTN_OUTLINE, \"w-8 p-0 shrink-0\")}\n aria-label=\"More actions\"\n >\n <MoreHorizontal className=\"h-3.5 w-3.5\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n\n <DropdownMenuItem asChild>\n <Link href={assetHref} className=\"flex items-center gap-2\">\n <ArrowUpRight className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View asset\n </Link>\n </DropdownMenuItem>\n\n <DropdownMenuSeparator />\n\n {isOwner ? (\n <>\n {!listingOrder && onList && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-purple focus:text-brand-purple\"\n onClick={() => onList(token)}\n >\n <Tag className=\"h-3.5 w-3.5\" />\n List\n </DropdownMenuItem>\n )}\n {onTransfer && (\n <DropdownMenuItem\n className=\"flex items-center gap-2\"\n onClick={() => onTransfer(token)}\n >\n <ArrowRightLeft className=\"h-3.5 w-3.5 text-muted-foreground\" />\n Transfer\n </DropdownMenuItem>\n )}\n {listingOrder && onCancel && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-orange focus:text-brand-orange\"\n onClick={() => onCancel(token)}\n >\n <XCircle className=\"h-3.5 w-3.5\" />\n Cancel listing\n </DropdownMenuItem>\n )}\n </>\n ) : (\n <>\n {listingOrder && onBuy && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-blue focus:text-brand-blue\"\n onClick={handleBuy}\n >\n <Zap className=\"h-3.5 w-3.5\" />\n <span className=\"flex items-center gap-1\">\n Buy —\n <CurrencyIcon symbol={listingOrder.price.currency} size={12} />\n {formatDisplayPrice(listingOrder.price.formatted)}\n </span>\n </DropdownMenuItem>\n )}\n {onOffer && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-orange focus:text-brand-orange\"\n onClick={handleOffer}\n >\n <HandCoins className=\"h-3.5 w-3.5\" />\n Make offer\n </DropdownMenuItem>\n )}\n </>\n )}\n\n <DropdownMenuItem asChild>\n <Link href={remixHref} className=\"flex items-center gap-2 text-brand-purple focus:text-brand-purple\">\n <GitBranch className=\"h-3.5 w-3.5\" />\n Remix\n </Link>\n </DropdownMenuItem>\n\n <DropdownMenuSeparator />\n\n <DropdownMenuItem asChild>\n <Link href={collectionHref} className=\"flex items-center gap-2\">\n <Layers className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View collection\n </Link>\n </DropdownMenuItem>\n\n {(owner ?? creator) && (\n <DropdownMenuItem asChild>\n <Link href={`/account/${owner ?? creator}`} className=\"flex items-center gap-2\">\n <UserCircle2 className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View owner\n </Link>\n </DropdownMenuItem>\n )}\n\n {onReport && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-muted-foreground focus:text-muted-foreground\"\n onClick={() => onReport(token)}\n >\n <Flag className=\"h-3.5 w-3.5\" />\n Report\n </DropdownMenuItem>\n </>\n )}\n\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n );\n}\n\nexport function TokenCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"aspect-square w-full animate-pulse bg-muted\" />\n <div className=\"px-3 pt-2.5 pb-3 space-y-1.5\">\n <div className=\"h-4 w-3/4 rounded-md animate-pulse bg-muted\" />\n <div className=\"h-2.5 w-2/5 rounded-md animate-pulse bg-muted\" />\n </div>\n <div className=\"px-3 pb-3 flex gap-1.5\">\n <div className=\"h-8 w-8 rounded-[9px] animate-pulse bg-muted shrink-0\" />\n <div className=\"h-8 flex-1 rounded-[9px] animate-pulse bg-muted\" />\n <div className=\"h-8 w-8 rounded-[9px] animate-pulse bg-muted shrink-0\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["buildAssetHref","buildCollectionHref","Link","Image"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/token-card.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport {\n Loader2, ArrowUpRight, MoreHorizontal, Zap, HandCoins, Tag,\n Layers, GitBranch, Flag, UserCircle2, ArrowRightLeft, XCircle,\n} from \"lucide-react\";\nimport {\n DropdownMenu, DropdownMenuContent, DropdownMenuItem,\n DropdownMenuSeparator, DropdownMenuTrigger,\n} from \"./dropdown-menu.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport { IpTypeBadge } from \"./ip-type-badge.js\";\nimport { AnimatedTokenMedia } from \"./animated-token-media.js\";\nimport { isLivingRenderCollection } from \"../data/living-render-collections.js\";\nimport { assetHref as buildAssetHref, collectionHref as buildCollectionHref } from \"@medialane/sdk\";\nimport type { ApiToken, Chain } from \"@medialane/sdk\";\n\nconst BTN_BASE = \"h-8 rounded-[9px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0\";\nconst BTN_OUTLINE = cn(BTN_BASE, \"border border-border/60 text-foreground hover:bg-muted/60\");\n\nexport interface TokenCardProps {\n token: ApiToken;\n isOwner?: boolean;\n onList?: (token: ApiToken) => void;\n onTransfer?: (token: ApiToken) => void;\n onCancel?: (token: ApiToken) => void;\n onBuy?: (token: ApiToken) => void;\n onOffer?: (token: ApiToken) => void;\n onReport?: (token: ApiToken) => void;\n}\n\nexport function TokenCard({\n token,\n isOwner = false,\n onList,\n onTransfer,\n onCancel,\n onBuy,\n onOffer,\n onReport,\n}: TokenCardProps) {\n const name = token.metadata?.name || `Token #${token.tokenId}`;\n const image = ipfsToHttp(token.metadata?.image);\n const animationUrl = token.metadata?.animationUrl ?? null;\n const ipType = token.metadata?.ipType;\n\n // First listing-type active order (offer.itemType = ERC721 or ERC1155)\n const listingOrder = token.activeOrders?.find(\n (o) => o.offer.itemType === \"ERC721\" || o.offer.itemType === \"ERC1155\"\n );\n\n const creatorAttr = token.metadata?.attributes?.find((a) => a.trait_type === \"Creator\");\n const creator = typeof creatorAttr?.value === \"string\" ? creatorAttr.value : null;\n const owner = token.balances?.[0]?.owner ?? token.owner ?? null;\n\n const chain = token.chain.toUpperCase() as Chain;\n const live = isLivingRenderCollection(chain, token.contractAddress);\n const assetHref = buildAssetHref(chain, token.contractAddress, token.tokenId);\n const collectionHref = buildCollectionHref(chain, token.contractAddress);\n const remixHref = `/create/remix/${token.contractAddress}/${token.tokenId}`;\n\n const handleBuy = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onBuy?.(token);\n };\n\n const handleOffer = (e: React.MouseEvent) => {\n e.preventDefault();\n e.stopPropagation();\n onOffer?.(token);\n };\n\n return (\n <div className=\"card-base relative overflow-hidden flex flex-col w-full\">\n\n {/* ── Clickable image + info ─────────────────────────────── */}\n <Link href={assetHref} className=\"flex flex-col flex-1\">\n\n {/* Image */}\n <div className=\"relative aspect-square bg-muted overflow-hidden shrink-0\">\n <AnimatedTokenMedia\n image={image}\n animationUrl={animationUrl}\n live={live}\n alt={name}\n mode=\"fill\"\n sizes=\"(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw\"\n className=\"object-cover\"\n fallback={\n <div className=\"absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15\">\n <span className=\"text-2xl tabular-nums text-muted-foreground\">#{token.tokenId}</span>\n </div>\n }\n />\n\n {/* Price chip — bottom right overlay */}\n {listingOrder && (\n <div className=\"absolute bottom-2 right-2 z-10\">\n <span className=\"inline-flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-bold bg-background/90 backdrop-blur-sm border border-border/40 shadow-sm\">\n <CurrencyIcon symbol={listingOrder.price.currency ?? \"\"} size={10} />\n {formatDisplayPrice(listingOrder.price.formatted)}\n <span className=\"text-muted-foreground font-normal\">{listingOrder.price.currency}</span>\n </span>\n </div>\n )}\n\n {/* Indexing indicator */}\n {(token.metadataStatus === \"PENDING\" || token.metadataStatus === \"FETCHING\") && (\n <div className=\"absolute bottom-0 inset-x-0 flex items-center justify-center gap-1.5 bg-black/50 backdrop-blur-sm py-1.5\">\n <Loader2 className=\"h-3 w-3 animate-spin text-white/70\" />\n <span className=\"text-[10px] text-white/70\">Indexing…</span>\n </div>\n )}\n </div>\n\n {/* Info */}\n <div className=\"px-3 pt-2.5 pb-4 flex-1\">\n <p className=\"text-base font-bold line-clamp-2 leading-snug\">{name}</p>\n <div className=\"flex items-center gap-1.5 mt-1.5 flex-wrap\">\n {ipType && <IpTypeBadge ipType={ipType as any} size=\"sm\" />}\n {creator ? (\n <p className=\"text-[10px] tabular-nums text-muted-foreground/60 truncate\">\n {creator.slice(0, 8)}…{creator.slice(-6)}\n </p>\n ) : token.metadata?.description ? (\n <p className=\"text-[10px] text-muted-foreground truncate leading-snug\">\n {token.metadata.description}\n </p>\n ) : null}\n </div>\n </div>\n </Link>\n\n {/* ── Action row ────────────────────────────────────────── */}\n <div className=\"px-3 pb-3 flex items-center gap-1.5\">\n\n {/* Expand */}\n <Link\n href={assetHref}\n className={cn(BTN_OUTLINE, \"w-8 shrink-0 px-0\")}\n title=\"View asset\"\n >\n <ArrowUpRight className=\"h-3.5 w-3.5\" />\n </Link>\n\n {/* Owner: List (or Listed label) — Buyer: Buy / Make offer */}\n {isOwner ? (\n listingOrder ? (\n <Link href={assetHref} className={cn(BTN_OUTLINE, \"flex-1\")}>\n <ArrowUpRight className=\"h-3.5 w-3.5 mr-1.5\" />\n View\n </Link>\n ) : (\n <button\n className={cn(BTN_OUTLINE, \"flex-1 text-brand-purple border-brand-purple/30 hover:bg-brand-purple/10 hover:text-brand-purple\")}\n onClick={(e) => { e.preventDefault(); e.stopPropagation(); onList?.(token); }}\n >\n <Tag className=\"h-3.5 w-3.5 mr-1.5\" />\n List\n </button>\n )\n ) : listingOrder ? (\n <div className=\"btn-border-animated p-[1.5px] rounded-[10px] flex-1 h-8\">\n <button\n className=\"w-full h-full rounded-[9px] bg-background flex items-center justify-center gap-1.5 text-xs font-semibold text-foreground hover:bg-muted/60 transition-all active:scale-[0.98]\"\n onClick={handleBuy}\n >\n <Zap className=\"h-3.5 w-3.5 shrink-0\" />\n Buy\n </button>\n </div>\n ) : (\n <button\n className={cn(BTN_OUTLINE, \"flex-1 text-brand-orange border-brand-orange/30 hover:bg-brand-orange/10 hover:text-brand-orange\")}\n onClick={handleOffer}\n >\n <HandCoins className=\"h-3.5 w-3.5 mr-1.5\" />\n Offer\n </button>\n )}\n\n {/* 3-dot menu */}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n className={cn(BTN_OUTLINE, \"w-8 p-0 shrink-0\")}\n aria-label=\"More actions\"\n >\n <MoreHorizontal className=\"h-3.5 w-3.5\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n\n <DropdownMenuItem asChild>\n <Link href={assetHref} className=\"flex items-center gap-2\">\n <ArrowUpRight className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View asset\n </Link>\n </DropdownMenuItem>\n\n <DropdownMenuSeparator />\n\n {isOwner ? (\n <>\n {!listingOrder && onList && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-purple focus:text-brand-purple\"\n onClick={() => onList(token)}\n >\n <Tag className=\"h-3.5 w-3.5\" />\n List\n </DropdownMenuItem>\n )}\n {onTransfer && (\n <DropdownMenuItem\n className=\"flex items-center gap-2\"\n onClick={() => onTransfer(token)}\n >\n <ArrowRightLeft className=\"h-3.5 w-3.5 text-muted-foreground\" />\n Transfer\n </DropdownMenuItem>\n )}\n {listingOrder && onCancel && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-orange focus:text-brand-orange\"\n onClick={() => onCancel(token)}\n >\n <XCircle className=\"h-3.5 w-3.5\" />\n Cancel listing\n </DropdownMenuItem>\n )}\n </>\n ) : (\n <>\n {listingOrder && onBuy && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-blue focus:text-brand-blue\"\n onClick={handleBuy}\n >\n <Zap className=\"h-3.5 w-3.5\" />\n <span className=\"flex items-center gap-1\">\n Buy —\n <CurrencyIcon symbol={listingOrder.price.currency} size={12} />\n {formatDisplayPrice(listingOrder.price.formatted)}\n </span>\n </DropdownMenuItem>\n )}\n {onOffer && (\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-brand-orange focus:text-brand-orange\"\n onClick={handleOffer}\n >\n <HandCoins className=\"h-3.5 w-3.5\" />\n Make offer\n </DropdownMenuItem>\n )}\n </>\n )}\n\n <DropdownMenuItem asChild>\n <Link href={remixHref} className=\"flex items-center gap-2 text-brand-purple focus:text-brand-purple\">\n <GitBranch className=\"h-3.5 w-3.5\" />\n Remix\n </Link>\n </DropdownMenuItem>\n\n <DropdownMenuSeparator />\n\n <DropdownMenuItem asChild>\n <Link href={collectionHref} className=\"flex items-center gap-2\">\n <Layers className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View collection\n </Link>\n </DropdownMenuItem>\n\n {(owner ?? creator) && (\n <DropdownMenuItem asChild>\n <Link href={`/account/${owner ?? creator}`} className=\"flex items-center gap-2\">\n <UserCircle2 className=\"h-3.5 w-3.5 text-muted-foreground\" />\n View owner\n </Link>\n </DropdownMenuItem>\n )}\n\n {onReport && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"flex items-center gap-2 text-muted-foreground focus:text-muted-foreground\"\n onClick={() => onReport(token)}\n >\n <Flag className=\"h-3.5 w-3.5\" />\n Report\n </DropdownMenuItem>\n </>\n )}\n\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n );\n}\n\nexport function TokenCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"aspect-square w-full animate-pulse bg-muted\" />\n <div className=\"px-3 pt-2.5 pb-3 space-y-1.5\">\n <div className=\"h-4 w-3/4 rounded-md animate-pulse bg-muted\" />\n <div className=\"h-2.5 w-2/5 rounded-md animate-pulse bg-muted\" />\n </div>\n <div className=\"px-3 pb-3 flex gap-1.5\">\n <div className=\"h-8 w-8 rounded-[9px] animate-pulse bg-muted shrink-0\" />\n <div className=\"h-8 flex-1 rounded-[9px] animate-pulse bg-muted\" />\n <div className=\"h-8 w-8 rounded-[9px] animate-pulse bg-muted shrink-0\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["buildAssetHref","buildCollectionHref","Link"]}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
4
3
|
import Link from "next/link";
|
|
5
|
-
import Image from "next/image";
|
|
6
4
|
import {
|
|
7
5
|
Loader2,
|
|
8
6
|
ArrowUpRight,
|
|
@@ -29,6 +27,8 @@ import { ipfsToHttp } from "../utils/ipfs.js";
|
|
|
29
27
|
import { formatDisplayPrice } from "../utils/format.js";
|
|
30
28
|
import { CurrencyIcon } from "./currency-icon.js";
|
|
31
29
|
import { IpTypeBadge } from "./ip-type-badge.js";
|
|
30
|
+
import { AnimatedTokenMedia } from "./animated-token-media.js";
|
|
31
|
+
import { isLivingRenderCollection } from "../data/living-render-collections.js";
|
|
32
32
|
import { assetHref as buildAssetHref, collectionHref as buildCollectionHref } from "@medialane/sdk";
|
|
33
33
|
const BTN_BASE = "h-8 rounded-[9px] flex items-center justify-center gap-1.5 text-xs font-semibold transition-all active:scale-[0.98] shadow-none border-0";
|
|
34
34
|
const BTN_OUTLINE = cn(BTN_BASE, "border border-border/60 text-foreground hover:bg-muted/60");
|
|
@@ -42,9 +42,9 @@ function TokenCard({
|
|
|
42
42
|
onOffer,
|
|
43
43
|
onReport
|
|
44
44
|
}) {
|
|
45
|
-
const [imgError, setImgError] = useState(false);
|
|
46
45
|
const name = token.metadata?.name || `Token #${token.tokenId}`;
|
|
47
46
|
const image = ipfsToHttp(token.metadata?.image);
|
|
47
|
+
const animationUrl = token.metadata?.animationUrl ?? null;
|
|
48
48
|
const ipType = token.metadata?.ipType;
|
|
49
49
|
const listingOrder = token.activeOrders?.find(
|
|
50
50
|
(o) => o.offer.itemType === "ERC721" || o.offer.itemType === "ERC1155"
|
|
@@ -53,6 +53,7 @@ function TokenCard({
|
|
|
53
53
|
const creator = typeof creatorAttr?.value === "string" ? creatorAttr.value : null;
|
|
54
54
|
const owner = token.balances?.[0]?.owner ?? token.owner ?? null;
|
|
55
55
|
const chain = token.chain.toUpperCase();
|
|
56
|
+
const live = isLivingRenderCollection(chain, token.contractAddress);
|
|
56
57
|
const assetHref = buildAssetHref(chain, token.contractAddress, token.tokenId);
|
|
57
58
|
const collectionHref = buildCollectionHref(chain, token.contractAddress);
|
|
58
59
|
const remixHref = `/create/remix/${token.contractAddress}/${token.tokenId}`;
|
|
@@ -69,21 +70,22 @@ function TokenCard({
|
|
|
69
70
|
return /* @__PURE__ */ jsxs("div", { className: "card-base relative overflow-hidden flex flex-col w-full", children: [
|
|
70
71
|
/* @__PURE__ */ jsxs(Link, { href: assetHref, className: "flex flex-col flex-1", children: [
|
|
71
72
|
/* @__PURE__ */ jsxs("div", { className: "relative aspect-square bg-muted overflow-hidden shrink-0", children: [
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
/* @__PURE__ */ jsx(
|
|
74
|
+
AnimatedTokenMedia,
|
|
74
75
|
{
|
|
75
|
-
|
|
76
|
+
image,
|
|
77
|
+
animationUrl,
|
|
78
|
+
live,
|
|
76
79
|
alt: name,
|
|
77
|
-
|
|
78
|
-
unoptimized: true,
|
|
80
|
+
mode: "fill",
|
|
79
81
|
sizes: "(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 22vw",
|
|
80
82
|
className: "object-cover",
|
|
81
|
-
|
|
83
|
+
fallback: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-brand-purple/15 to-brand-blue/15", children: /* @__PURE__ */ jsxs("span", { className: "text-2xl tabular-nums text-muted-foreground", children: [
|
|
84
|
+
"#",
|
|
85
|
+
token.tokenId
|
|
86
|
+
] }) })
|
|
82
87
|
}
|
|
83
|
-
)
|
|
84
|
-
"#",
|
|
85
|
-
token.tokenId
|
|
86
|
-
] }) }),
|
|
88
|
+
),
|
|
87
89
|
listingOrder && /* @__PURE__ */ jsx("div", { className: "absolute bottom-2 right-2 z-10", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-1 rounded-full text-[11px] font-bold bg-background/90 backdrop-blur-sm border border-border/40 shadow-sm", children: [
|
|
88
90
|
/* @__PURE__ */ jsx(CurrencyIcon, { symbol: listingOrder.price.currency ?? "", size: 10 }),
|
|
89
91
|
formatDisplayPrice(listingOrder.price.formatted),
|