@medialane/ui 0.23.0 → 0.24.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/asset-overview-content.cjs +1 -1
- package/dist/components/asset-overview-content.cjs.map +1 -1
- package/dist/components/asset-overview-content.js +1 -1
- package/dist/components/asset-overview-content.js.map +1 -1
- package/dist/components/coin-card.cjs +69 -51
- package/dist/components/coin-card.cjs.map +1 -1
- package/dist/components/coin-card.js +70 -53
- package/dist/components/coin-card.js.map +1 -1
- package/dist/utils/license-summary.cjs +4 -1
- package/dist/utils/license-summary.cjs.map +1 -1
- package/dist/utils/license-summary.js +4 -1
- package/dist/utils/license-summary.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,7 +27,7 @@ var import_lucide_react = require("lucide-react");
|
|
|
27
27
|
var import_ip_type_display = require("./ip-type-display.js");
|
|
28
28
|
var import_address_display = require("./address-display.js");
|
|
29
29
|
var import_license_summary = require("../utils/license-summary.js");
|
|
30
|
-
const isAddressLike = (v) =>
|
|
30
|
+
const isAddressLike = (v) => typeof v === "string" && /^0x[0-9a-fA-F]{16,}$/.test(v.trim());
|
|
31
31
|
function Cell({
|
|
32
32
|
icon,
|
|
33
33
|
label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/asset-overview-content.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Bot, Calendar, DollarSign, GitBranch, Globe, Percent, UserCheck } from \"lucide-react\";\nimport { IPTypeDisplay } from \"./ip-type-display.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { licenseSummary } from \"../utils/license-summary.js\";\n\ninterface AssetAttribute {\n trait_type?: string;\n value?: string;\n}\n\ninterface AssetOverviewContentProps {\n attributes: AssetAttribute[];\n hasTemplateData: boolean;\n isDisplayAttr: (attribute: AssetAttribute) => boolean;\n}\n\nconst isAddressLike = (v?:
|
|
1
|
+
{"version":3,"sources":["../../src/components/asset-overview-content.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Bot, Calendar, DollarSign, GitBranch, Globe, Percent, UserCheck } from \"lucide-react\";\nimport { IPTypeDisplay } from \"./ip-type-display.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { licenseSummary } from \"../utils/license-summary.js\";\n\ninterface AssetAttribute {\n trait_type?: string;\n value?: string;\n}\n\ninterface AssetOverviewContentProps {\n attributes: AssetAttribute[];\n hasTemplateData: boolean;\n isDisplayAttr: (attribute: AssetAttribute) => boolean;\n}\n\n// Attribute values can be numbers (OpenSea allows numeric trait `value`s), so\n// guard against non-strings — `(123).trim()` would throw and crash the page.\nconst isAddressLike = (v?: unknown): boolean =>\n typeof v === \"string\" && /^0x[0-9a-fA-F]{16,}$/.test(v.trim());\n\n/** A standard bento square: a brand-tinted icon, an uppercase label, a bold value.\n * `wide` stretches it across two columns for an emphasized detail. */\nfunction Cell({\n icon,\n label,\n value,\n wide,\n}: {\n icon?: ReactNode;\n label: string;\n value: ReactNode;\n wide?: boolean;\n}) {\n return (\n <div\n className={`flex flex-col justify-between gap-2 rounded-xl bg-muted/25 ring-1 ring-border/40 p-3.5 ${\n wide ? \"col-span-2\" : \"\"\n }`}\n >\n {icon ? <span className=\"text-primary/70\">{icon}</span> : null}\n <div className=\"space-y-0.5\">\n <p className=\"text-[10px] font-medium uppercase tracking-wider text-muted-foreground truncate\" title={label}>\n {label}\n </p>\n <div className=\"text-sm font-bold text-foreground truncate\">{value}</div>\n </div>\n </div>\n );\n}\n\n/**\n * Asset Overview tab, as a Bento 2.0 grid: an asymmetrical lattice of rounded\n * compartments on a soft brand light-leak, uniform gaps throughout. The license\n * summary is the emphasized cell (stretched 2×2, brand-gradient wash + license\n * stamp + emerald trust seal); each right is a standard square; Details follow.\n * Uses only design-system tokens (brand-* / primary / aurora / emerald-for-trust).\n */\nexport function AssetOverviewContent({\n attributes,\n hasTemplateData,\n isDisplayAttr,\n}: AssetOverviewContentProps) {\n const attr = (trait: string) => attributes.find((attribute) => attribute.trait_type === trait)?.value;\n const licenseType = attr(\"License\");\n const commercialUse = attr(\"Commercial Use\");\n const derivatives = attr(\"Derivatives\");\n const attribution = attr(\"Attribution\");\n const territory = attr(\"Territory\");\n const aiPolicy = attr(\"AI Policy\");\n const royalty = attr(\"Royalty\");\n const registration = attr(\"Registration\");\n const summary = licenseSummary(attributes);\n const hasLicenseData = !!(licenseType || commercialUse || derivatives || attribution);\n const displayAttributes = attributes.filter((attribute) => isDisplayAttr(attribute));\n\n const facts = [\n { icon: <DollarSign className=\"h-4 w-4\" />, label: \"Commercial use\", value: commercialUse },\n { icon: <GitBranch className=\"h-4 w-4\" />, label: \"Derivatives\", value: derivatives },\n { icon: <UserCheck className=\"h-4 w-4\" />, label: \"Attribution\", value: attribution },\n { icon: <Globe className=\"h-4 w-4\" />, label: \"Territory\", value: territory },\n { icon: <Bot className=\"h-4 w-4\" />, label: \"AI & data mining\", value: aiPolicy },\n { icon: <Percent className=\"h-4 w-4\" />, label: \"Royalty\", value: royalty },\n { icon: <Calendar className=\"h-4 w-4\" />, label: \"Registered\", value: registration },\n ].filter((row) => !!row.value);\n\n return (\n <div className=\"mt-4 space-y-7\">\n {hasTemplateData ? <IPTypeDisplay attributes={attributes} /> : null}\n\n {hasLicenseData ? (\n <section className=\"space-y-3\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">Rights</h3>\n <div className=\"relative\">\n {/* soft brand light-leak behind the lattice */}\n <div\n aria-hidden\n className=\"aurora-purple pointer-events-none\"\n style={{ position: \"absolute\", width: 260, height: 260, top: -40, left: \"30%\" }}\n />\n <div className=\"relative grid grid-cols-2 sm:grid-cols-4 gap-3 [grid-auto-flow:dense] auto-rows-[minmax(6rem,auto)]\">\n {/* emphasized cell — the license summary, stretched 2×2 */}\n <div className=\"col-span-2 sm:row-span-2 flex flex-col justify-between gap-4 rounded-2xl bg-gradient-to-br from-primary/10 via-primary/5 to-accent/10 ring-1 ring-primary/15 p-5\">\n {licenseType ? (\n <span className=\"pill-badge self-start text-[10px] uppercase tracking-wider\">{licenseType}</span>\n ) : null}\n {summary ? (\n <p className=\"text-base sm:text-lg font-semibold leading-snug text-foreground\">{summary}</p>\n ) : null}\n </div>\n\n {facts.map(({ icon, label, value }) => (\n <Cell key={label} icon={icon} label={label} value={value} />\n ))}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground/70\">\n Kept in permanent, tamper-proof storage · recognized under international copyright law\n </p>\n </section>\n ) : null}\n\n {displayAttributes.length > 0 ? (\n <section className=\"space-y-3\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">Details</h3>\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 [grid-auto-flow:dense] auto-rows-[minmax(6rem,auto)]\">\n {displayAttributes.map((attribute, index) => (\n <Cell\n key={index}\n label={attribute.trait_type ?? \"Trait\"}\n wide={isAddressLike(attribute.value)}\n value={\n isAddressLike(attribute.value) ? (\n <AddressDisplay address={attribute.value!} chars={4} className=\"text-sm font-bold\" />\n ) : (\n attribute.value ?? \"—\"\n )\n }\n />\n ))}\n </div>\n </section>\n ) : null}\n\n {!hasTemplateData && !hasLicenseData && displayAttributes.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">No additional details available.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -4,7 +4,7 @@ import { Bot, Calendar, DollarSign, GitBranch, Globe, Percent, UserCheck } from
|
|
|
4
4
|
import { IPTypeDisplay } from "./ip-type-display.js";
|
|
5
5
|
import { AddressDisplay } from "./address-display.js";
|
|
6
6
|
import { licenseSummary } from "../utils/license-summary.js";
|
|
7
|
-
const isAddressLike = (v) =>
|
|
7
|
+
const isAddressLike = (v) => typeof v === "string" && /^0x[0-9a-fA-F]{16,}$/.test(v.trim());
|
|
8
8
|
function Cell({
|
|
9
9
|
icon,
|
|
10
10
|
label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/asset-overview-content.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Bot, Calendar, DollarSign, GitBranch, Globe, Percent, UserCheck } from \"lucide-react\";\nimport { IPTypeDisplay } from \"./ip-type-display.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { licenseSummary } from \"../utils/license-summary.js\";\n\ninterface AssetAttribute {\n trait_type?: string;\n value?: string;\n}\n\ninterface AssetOverviewContentProps {\n attributes: AssetAttribute[];\n hasTemplateData: boolean;\n isDisplayAttr: (attribute: AssetAttribute) => boolean;\n}\n\nconst isAddressLike = (v?:
|
|
1
|
+
{"version":3,"sources":["../../src/components/asset-overview-content.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ReactNode } from \"react\";\nimport { Bot, Calendar, DollarSign, GitBranch, Globe, Percent, UserCheck } from \"lucide-react\";\nimport { IPTypeDisplay } from \"./ip-type-display.js\";\nimport { AddressDisplay } from \"./address-display.js\";\nimport { licenseSummary } from \"../utils/license-summary.js\";\n\ninterface AssetAttribute {\n trait_type?: string;\n value?: string;\n}\n\ninterface AssetOverviewContentProps {\n attributes: AssetAttribute[];\n hasTemplateData: boolean;\n isDisplayAttr: (attribute: AssetAttribute) => boolean;\n}\n\n// Attribute values can be numbers (OpenSea allows numeric trait `value`s), so\n// guard against non-strings — `(123).trim()` would throw and crash the page.\nconst isAddressLike = (v?: unknown): boolean =>\n typeof v === \"string\" && /^0x[0-9a-fA-F]{16,}$/.test(v.trim());\n\n/** A standard bento square: a brand-tinted icon, an uppercase label, a bold value.\n * `wide` stretches it across two columns for an emphasized detail. */\nfunction Cell({\n icon,\n label,\n value,\n wide,\n}: {\n icon?: ReactNode;\n label: string;\n value: ReactNode;\n wide?: boolean;\n}) {\n return (\n <div\n className={`flex flex-col justify-between gap-2 rounded-xl bg-muted/25 ring-1 ring-border/40 p-3.5 ${\n wide ? \"col-span-2\" : \"\"\n }`}\n >\n {icon ? <span className=\"text-primary/70\">{icon}</span> : null}\n <div className=\"space-y-0.5\">\n <p className=\"text-[10px] font-medium uppercase tracking-wider text-muted-foreground truncate\" title={label}>\n {label}\n </p>\n <div className=\"text-sm font-bold text-foreground truncate\">{value}</div>\n </div>\n </div>\n );\n}\n\n/**\n * Asset Overview tab, as a Bento 2.0 grid: an asymmetrical lattice of rounded\n * compartments on a soft brand light-leak, uniform gaps throughout. The license\n * summary is the emphasized cell (stretched 2×2, brand-gradient wash + license\n * stamp + emerald trust seal); each right is a standard square; Details follow.\n * Uses only design-system tokens (brand-* / primary / aurora / emerald-for-trust).\n */\nexport function AssetOverviewContent({\n attributes,\n hasTemplateData,\n isDisplayAttr,\n}: AssetOverviewContentProps) {\n const attr = (trait: string) => attributes.find((attribute) => attribute.trait_type === trait)?.value;\n const licenseType = attr(\"License\");\n const commercialUse = attr(\"Commercial Use\");\n const derivatives = attr(\"Derivatives\");\n const attribution = attr(\"Attribution\");\n const territory = attr(\"Territory\");\n const aiPolicy = attr(\"AI Policy\");\n const royalty = attr(\"Royalty\");\n const registration = attr(\"Registration\");\n const summary = licenseSummary(attributes);\n const hasLicenseData = !!(licenseType || commercialUse || derivatives || attribution);\n const displayAttributes = attributes.filter((attribute) => isDisplayAttr(attribute));\n\n const facts = [\n { icon: <DollarSign className=\"h-4 w-4\" />, label: \"Commercial use\", value: commercialUse },\n { icon: <GitBranch className=\"h-4 w-4\" />, label: \"Derivatives\", value: derivatives },\n { icon: <UserCheck className=\"h-4 w-4\" />, label: \"Attribution\", value: attribution },\n { icon: <Globe className=\"h-4 w-4\" />, label: \"Territory\", value: territory },\n { icon: <Bot className=\"h-4 w-4\" />, label: \"AI & data mining\", value: aiPolicy },\n { icon: <Percent className=\"h-4 w-4\" />, label: \"Royalty\", value: royalty },\n { icon: <Calendar className=\"h-4 w-4\" />, label: \"Registered\", value: registration },\n ].filter((row) => !!row.value);\n\n return (\n <div className=\"mt-4 space-y-7\">\n {hasTemplateData ? <IPTypeDisplay attributes={attributes} /> : null}\n\n {hasLicenseData ? (\n <section className=\"space-y-3\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">Rights</h3>\n <div className=\"relative\">\n {/* soft brand light-leak behind the lattice */}\n <div\n aria-hidden\n className=\"aurora-purple pointer-events-none\"\n style={{ position: \"absolute\", width: 260, height: 260, top: -40, left: \"30%\" }}\n />\n <div className=\"relative grid grid-cols-2 sm:grid-cols-4 gap-3 [grid-auto-flow:dense] auto-rows-[minmax(6rem,auto)]\">\n {/* emphasized cell — the license summary, stretched 2×2 */}\n <div className=\"col-span-2 sm:row-span-2 flex flex-col justify-between gap-4 rounded-2xl bg-gradient-to-br from-primary/10 via-primary/5 to-accent/10 ring-1 ring-primary/15 p-5\">\n {licenseType ? (\n <span className=\"pill-badge self-start text-[10px] uppercase tracking-wider\">{licenseType}</span>\n ) : null}\n {summary ? (\n <p className=\"text-base sm:text-lg font-semibold leading-snug text-foreground\">{summary}</p>\n ) : null}\n </div>\n\n {facts.map(({ icon, label, value }) => (\n <Cell key={label} icon={icon} label={label} value={value} />\n ))}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground/70\">\n Kept in permanent, tamper-proof storage · recognized under international copyright law\n </p>\n </section>\n ) : null}\n\n {displayAttributes.length > 0 ? (\n <section className=\"space-y-3\">\n <h3 className=\"text-xs font-semibold uppercase tracking-wider text-muted-foreground\">Details</h3>\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 [grid-auto-flow:dense] auto-rows-[minmax(6rem,auto)]\">\n {displayAttributes.map((attribute, index) => (\n <Cell\n key={index}\n label={attribute.trait_type ?? \"Trait\"}\n wide={isAddressLike(attribute.value)}\n value={\n isAddressLike(attribute.value) ? (\n <AddressDisplay address={attribute.value!} chars={4} className=\"text-sm font-bold\" />\n ) : (\n attribute.value ?? \"—\"\n )\n }\n />\n ))}\n </div>\n </section>\n ) : null}\n\n {!hasTemplateData && !hasLicenseData && displayAttributes.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">No additional details available.</p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -37,89 +37,107 @@ module.exports = __toCommonJS(coin_card_exports);
|
|
|
37
37
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
38
38
|
var import_link = __toESM(require("next/link"), 1);
|
|
39
39
|
var import_image = __toESM(require("next/image"), 1);
|
|
40
|
-
var import_lucide_react = require("lucide-react");
|
|
41
40
|
var import_cn = require("../utils/cn.js");
|
|
42
41
|
var import_ipfs = require("../utils/ipfs.js");
|
|
42
|
+
var import_motion_primitives = require("./motion-primitives.js");
|
|
43
43
|
var import_coins = require("../data/coins.js");
|
|
44
44
|
const KIND_TAG = {
|
|
45
45
|
creator: "border-brand-purple/30 bg-brand-purple/10 text-brand-purple",
|
|
46
46
|
memecoin: "border-brand-rose/30 bg-brand-rose/10 text-brand-rose"
|
|
47
47
|
};
|
|
48
|
+
const KIND_GLOW = {
|
|
49
|
+
creator: "bg-brand-purple/25",
|
|
50
|
+
memecoin: "bg-brand-rose/25"
|
|
51
|
+
};
|
|
52
|
+
const KIND_FALLBACK = {
|
|
53
|
+
creator: "from-brand-blue to-brand-purple",
|
|
54
|
+
memecoin: "from-brand-purple to-brand-rose"
|
|
55
|
+
};
|
|
48
56
|
function useTileModel({ collection, usePrice }) {
|
|
49
57
|
const { price, isLoading } = usePrice(collection);
|
|
50
58
|
const kind = (0, import_coins.coinKind)(collection.service);
|
|
51
|
-
const verified = collection.claimedBy != null || kind === "creator";
|
|
52
59
|
const logoUri = collection.profile?.image ?? collection.image;
|
|
53
60
|
const logo = logoUri ? (0, import_ipfs.ipfsToHttp)(logoUri) : null;
|
|
54
61
|
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
55
|
-
const fdv = (0, import_coins.formatFdv)(price?.quotePerCoin, collection.totalSupply, price?.quoteSymbol ?? null);
|
|
56
62
|
const chain = (collection.chain ?? "").toString().toLowerCase();
|
|
57
|
-
return { price, isLoading, kind,
|
|
63
|
+
return { price, isLoading, kind, logo, initials, chain };
|
|
58
64
|
}
|
|
59
|
-
function PriceSkel(
|
|
60
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className:
|
|
65
|
+
function PriceSkel() {
|
|
66
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse" });
|
|
67
|
+
}
|
|
68
|
+
function KindChip({ kind, className }) {
|
|
69
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium", KIND_TAG[kind], className), children: kind === "creator" ? "Creator Coin" : "Memecoin" });
|
|
61
70
|
}
|
|
62
71
|
function CoinCard({ collection, usePrice, href }) {
|
|
63
72
|
const m = useTileModel({ collection, usePrice });
|
|
64
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
65
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "group relative h-full", children: [
|
|
74
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
"aria-hidden": true,
|
|
78
|
+
className: (0, import_cn.cn)(
|
|
79
|
+
"pointer-events-none absolute -inset-2 rounded-[1.4rem] opacity-60 blur-xl transition-opacity duration-500 group-hover:opacity-100",
|
|
80
|
+
KIND_GLOW[m.kind]
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_motion_primitives.MotionCard, { className: "card-base relative flex h-full flex-col", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href, className: "flex h-full flex-col", children: [
|
|
85
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-square w-full overflow-hidden bg-muted", children: [
|
|
86
|
+
m.logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
87
|
+
import_image.default,
|
|
88
|
+
{
|
|
89
|
+
src: m.logo,
|
|
90
|
+
alt: collection.name ?? "Coin",
|
|
91
|
+
fill: true,
|
|
92
|
+
sizes: "(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw",
|
|
93
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
94
|
+
unoptimized: true
|
|
95
|
+
}
|
|
96
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("absolute inset-0 flex items-center justify-center bg-gradient-to-br", KIND_FALLBACK[m.kind]), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "select-none text-5xl font-black tracking-tighter text-white/90", children: m.initials }) }),
|
|
97
|
+
m.chain && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute bottom-2 left-2 rounded-full bg-black/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-white/80 backdrop-blur-md", children: m.chain })
|
|
73
98
|
] }),
|
|
74
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-1 flex-col gap-3 p-3", children: [
|
|
100
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-2", children: [
|
|
101
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
|
|
102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "truncate text-sm text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KindChip, { kind: m.kind })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mt-auto space-y-2", children: [
|
|
108
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
109
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Price" }),
|
|
110
|
+
m.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceSkel, {}) : m.price ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-semibold tabular-nums", children: (0, import_coins.formatCoinPrice)(m.price.quotePerCoin) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
111
|
+
] }),
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple px-3 py-2 text-center text-sm font-semibold text-white", children: "Trade" })
|
|
113
|
+
] })
|
|
114
|
+
] })
|
|
115
|
+
] }) })
|
|
88
116
|
] });
|
|
89
117
|
}
|
|
90
118
|
function CoinRow({ collection, usePrice, href }) {
|
|
91
119
|
const m = useTileModel({ collection, usePrice });
|
|
92
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href, className: "flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40", children: [
|
|
93
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted", children: m.logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: m.logo, alt: "", fill: true, sizes: "36px", className: "object-cover", unoptimized: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br
|
|
120
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href, className: "flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40 active:scale-[0.99]", children: [
|
|
121
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted", children: m.logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: m.logo, alt: "", fill: true, sizes: "36px", className: "object-cover", unoptimized: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: (0, import_cn.cn)("flex h-full w-full items-center justify-center bg-gradient-to-br text-[11px] font-bold text-white", KIND_FALLBACK[m.kind]), children: m.initials }) }),
|
|
94
122
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0 flex-1", children: [
|
|
95
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
96
|
-
|
|
97
|
-
m.verified && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.BadgeCheck, { className: "h-3.5 w-3.5 shrink-0 text-primary", "aria-label": "Verified" })
|
|
98
|
-
] }),
|
|
99
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate text-sm font-semibold", children: collection.name ?? "Untitled coin" }),
|
|
124
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block truncate text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
100
125
|
] }),
|
|
101
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(KindChip, { kind: m.kind, className: "hidden sm:inline-block" }),
|
|
102
127
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-24 shrink-0 text-right text-sm font-semibold tabular-nums", children: m.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse" }) : m.price ? (0, import_coins.formatCoinPrice)(m.price.quotePerCoin) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground", children: "\u2014" }) }),
|
|
103
128
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hidden shrink-0 text-sm font-medium text-primary sm:inline-block", children: "Trade" })
|
|
104
129
|
] });
|
|
105
130
|
}
|
|
106
|
-
function Stat({ label, children }) {
|
|
107
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-0.5", children: [
|
|
108
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: label }),
|
|
109
|
-
children
|
|
110
|
-
] });
|
|
111
|
-
}
|
|
112
131
|
function CoinCardSkeleton() {
|
|
113
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col
|
|
114
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
115
|
-
|
|
116
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "
|
|
132
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-base flex flex-col", children: [
|
|
133
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "aspect-square w-full animate-pulse bg-muted" }),
|
|
134
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-3 p-3", children: [
|
|
135
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5", children: [
|
|
117
136
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-4 w-24 rounded bg-muted-foreground/20 animate-pulse" }),
|
|
118
137
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-3 w-12 rounded bg-muted-foreground/20 animate-pulse" })
|
|
119
|
-
] })
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-9 w-full bg-muted-foreground/10 animate-pulse" })
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-9 w-full rounded-lg bg-muted-foreground/10 animate-pulse" })
|
|
140
|
+
] })
|
|
123
141
|
] });
|
|
124
142
|
}
|
|
125
143
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/coin-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinCard / CoinRow — chain-agnostic coin discovery tiles.\n *\n * Pure presentation: the price read (`usePrice`) and the link target (`href`)\n * are injected by the consuming app, and the collection is typed structurally\n * (CoinCollectionLike) — so a coin on Starknet, Ethereum, or Solana renders the\n * same with zero changes here. `chain` is shown as a badge.\n */\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { BadgeCheck, Users } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport {\n coinKind, formatCoinPrice, formatFdv,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\n\n/** Injected per-chain spot-price read. `price` is null while loading/unknown. */\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport interface CoinTileProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n /** Link target — internal coin page or the per-chain trading app. */\n href: string;\n}\n\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n memecoin: \"border-brand-rose/30 bg-brand-rose/10 text-brand-rose\",\n};\n\nfunction useTileModel({ collection, usePrice }: Omit<CoinTileProps, \"href\">) {\n const { price, isLoading } = usePrice(collection);\n const kind = coinKind(collection.service);\n const verified = collection.claimedBy != null || kind === \"creator\";\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const fdv = formatFdv(price?.quotePerCoin, collection.totalSupply, price?.quoteSymbol ?? null);\n const chain = (collection.chain ?? \"\").toString().toLowerCase();\n return { price, isLoading, kind, verified, logo, initials, fdv, chain };\n}\n\nfunction PriceSkel({ wide }: { wide?: boolean }) {\n return <span className={cn(\"inline-block h-4 rounded bg-muted-foreground/20 animate-pulse\", wide ? \"w-16\" : \"w-12\")} />;\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex flex-col rounded-xl border border-border/60 bg-card overflow-hidden transition-transform active:scale-[0.99]\">\n <div className=\"flex items-center gap-3 p-4\">\n <div className=\"relative h-12 w-12 shrink-0 rounded-full overflow-hidden bg-muted\">\n {m.logo ? (\n <Image src={m.logo} alt=\"\" fill sizes=\"48px\" className=\"object-cover\" unoptimized />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-brand-blue to-brand-purple text-sm font-bold text-white\">{m.initials}</div>\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n {m.verified && <BadgeCheck className=\"h-4 w-4 shrink-0 text-primary\" aria-label=\"Verified\" />}\n </div>\n <span className=\"text-sm text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <span className={cn(\"shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium\", KIND_TAG[m.kind])}>\n {m.kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n </div>\n <div className=\"grid grid-cols-3 gap-2 border-t border-border/60 px-4 py-3 text-sm\">\n <Stat label=\"Price\">\n {m.isLoading ? <PriceSkel /> : m.price ? <span className=\"font-semibold\">{formatCoinPrice(m.price.quotePerCoin)}</span> : <span className=\"text-muted-foreground\">—</span>}\n </Stat>\n <Stat label=\"FDV\">\n {m.isLoading ? <PriceSkel /> : <span className=\"font-semibold\">{m.fdv ?? \"—\"}</span>}\n </Stat>\n <Stat label=\"Holders\">\n <span className=\"inline-flex items-center gap-1 font-semibold\">\n <Users className=\"h-3 w-3 text-muted-foreground\" />{collection.holderCount || \"—\"}\n </span>\n </Stat>\n </div>\n <div className=\"flex items-center justify-between border-t border-border/60 px-4 py-2.5 text-sm\">\n {m.chain ? <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{m.chain}</span> : <span />}\n <span className=\"font-medium text-primary\">Trade</span>\n </div>\n </Link>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40\">\n <div className=\"relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted\">\n {m.logo ? <Image src={m.logo} alt=\"\" fill sizes=\"36px\" className=\"object-cover\" unoptimized /> :\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-brand-blue to-brand-purple text-[11px] font-bold text-white\">{m.initials}</div>}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate text-sm font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n {m.verified && <BadgeCheck className=\"h-3.5 w-3.5 shrink-0 text-primary\" aria-label=\"Verified\" />}\n </div>\n <span className=\"text-xs text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <span className={cn(\"hidden shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium sm:inline-block\", KIND_TAG[m.kind])}>\n {m.kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n <span className=\"w-24 shrink-0 text-right text-sm font-semibold tabular-nums\">\n {m.isLoading ? <span className=\"ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse\" /> : m.price ? formatCoinPrice(m.price.quotePerCoin) : <span className=\"text-muted-foreground\">—</span>}\n </span>\n <span className=\"hidden shrink-0 text-sm font-medium text-primary sm:inline-block\">Trade</span>\n </Link>\n );\n}\n\nfunction Stat({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {children}\n </div>\n );\n}\n\nexport function CoinCardSkeleton() {\n return (\n <div className=\"flex flex-col rounded-xl border border-border/60 bg-card overflow-hidden\">\n <div className=\"flex items-center gap-3 p-4\">\n <div className=\"h-12 w-12 rounded-full bg-muted animate-pulse\" />\n <div className=\"flex-1 space-y-1.5\">\n <div className=\"h-4 w-24 rounded bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-12 rounded bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n <div className=\"grid grid-cols-3 gap-2 border-t border-border/60 px-4 py-3\">\n {[0, 1, 2].map((i) => <div key={i} className=\"h-8 w-full rounded bg-muted-foreground/10 animate-pulse\" />)}\n </div>\n <div className=\"h-9 w-full bg-muted-foreground/10 animate-pulse\" />\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/coin-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinCard / CoinRow — chain-agnostic, art-forward coin discovery tiles.\n *\n * Pure presentation: the price read (`usePrice`) and the link target (`href`)\n * are injected by the consuming app, and the collection is typed structurally\n * (CoinCollectionLike) — so a coin on Starknet, Ethereum, or Solana renders the\n * same with zero changes here. The coin's artwork is the hero; the only stat is\n * the live spot price (FDV / holders / a \"verified\" mark are intentionally not\n * shown — Medialane indexes none of them).\n */\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport {\n coinKind, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\n\n/** Injected per-chain spot-price read. `price` is null while loading/unknown. */\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport interface CoinTileProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n /** Link target — internal coin page or the per-chain trading app. */\n href: string;\n}\n\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n memecoin: \"border-brand-rose/30 bg-brand-rose/10 text-brand-rose\",\n};\n\n// Aurora glow + art-fallback gradient, tinted by kind (foundations §III: rose\n// is the coin surface, purple/blue the creator accent).\nconst KIND_GLOW: Record<string, string> = {\n creator: \"bg-brand-purple/25\",\n memecoin: \"bg-brand-rose/25\",\n};\nconst KIND_FALLBACK: Record<string, string> = {\n creator: \"from-brand-blue to-brand-purple\",\n memecoin: \"from-brand-purple to-brand-rose\",\n};\n\nfunction useTileModel({ collection, usePrice }: Omit<CoinTileProps, \"href\">) {\n const { price, isLoading } = usePrice(collection);\n const kind = coinKind(collection.service);\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const chain = (collection.chain ?? \"\").toString().toLowerCase();\n return { price, isLoading, kind, logo, initials, chain };\n}\n\nfunction PriceSkel() {\n return <span className=\"inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse\" />;\n}\n\nfunction KindChip({ kind, className }: { kind: string; className?: string }) {\n return (\n <span className={cn(\"shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium\", KIND_TAG[kind], className)}>\n {kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n );\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <div className=\"group relative h-full\">\n {/* Aurora glow — soft, kind-tinted, blooms past the card edge. */}\n <div\n aria-hidden\n className={cn(\n \"pointer-events-none absolute -inset-2 rounded-[1.4rem] opacity-60 blur-xl transition-opacity duration-500 group-hover:opacity-100\",\n KIND_GLOW[m.kind]\n )}\n />\n <MotionCard className=\"card-base relative flex h-full flex-col\">\n <Link href={href} className=\"flex h-full flex-col\">\n {/* Hero artwork */}\n <div className=\"relative aspect-square w-full overflow-hidden bg-muted\">\n {m.logo ? (\n <Image\n src={m.logo}\n alt={collection.name ?? \"Coin\"}\n fill\n sizes=\"(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw\"\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n unoptimized\n />\n ) : (\n <div className={cn(\"absolute inset-0 flex items-center justify-center bg-gradient-to-br\", KIND_FALLBACK[m.kind])}>\n <span className=\"select-none text-5xl font-black tracking-tighter text-white/90\">{m.initials}</span>\n </div>\n )}\n {m.chain && (\n <span className=\"absolute bottom-2 left-2 rounded-full bg-black/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-white/80 backdrop-blur-md\">\n {m.chain}\n </span>\n )}\n </div>\n\n {/* Body */}\n <div className=\"flex flex-1 flex-col gap-3 p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</div>\n <div className=\"truncate text-sm text-muted-foreground\">{collection.symbol ?? \"—\"}</div>\n </div>\n <KindChip kind={m.kind} />\n </div>\n <div className=\"mt-auto space-y-2\">\n <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Price</span>\n {m.isLoading ? <PriceSkel /> : m.price ? (\n <span className=\"font-semibold tabular-nums\">{formatCoinPrice(m.price.quotePerCoin)}</span>\n ) : (\n <span className=\"text-muted-foreground\">—</span>\n )}\n </div>\n <span className=\"block rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple px-3 py-2 text-center text-sm font-semibold text-white\">\n Trade\n </span>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40 active:scale-[0.99]\">\n <div className=\"relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted\">\n {m.logo ? <Image src={m.logo} alt=\"\" fill sizes=\"36px\" className=\"object-cover\" unoptimized /> :\n <div className={cn(\"flex h-full w-full items-center justify-center bg-gradient-to-br text-[11px] font-bold text-white\", KIND_FALLBACK[m.kind])}>{m.initials}</div>}\n </div>\n <div className=\"min-w-0 flex-1\">\n <span className=\"truncate text-sm font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <KindChip kind={m.kind} className=\"hidden sm:inline-block\" />\n <span className=\"w-24 shrink-0 text-right text-sm font-semibold tabular-nums\">\n {m.isLoading ? <span className=\"ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse\" /> : m.price ? formatCoinPrice(m.price.quotePerCoin) : <span className=\"text-muted-foreground\">—</span>}\n </span>\n <span className=\"hidden shrink-0 text-sm font-medium text-primary sm:inline-block\">Trade</span>\n </Link>\n );\n}\n\nexport function CoinCardSkeleton() {\n return (\n <div className=\"card-base flex flex-col\">\n <div className=\"aspect-square w-full animate-pulse bg-muted\" />\n <div className=\"flex flex-col gap-3 p-3\">\n <div className=\"space-y-1.5\">\n <div className=\"h-4 w-24 rounded bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-12 rounded bg-muted-foreground/20 animate-pulse\" />\n </div>\n <div className=\"h-9 w-full rounded-lg bg-muted-foreground/10 animate-pulse\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
|
|
@@ -2,93 +2,110 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import Link from "next/link";
|
|
4
4
|
import Image from "next/image";
|
|
5
|
-
import { BadgeCheck, Users } from "lucide-react";
|
|
6
5
|
import { cn } from "../utils/cn.js";
|
|
7
6
|
import { ipfsToHttp } from "../utils/ipfs.js";
|
|
7
|
+
import { MotionCard } from "./motion-primitives.js";
|
|
8
8
|
import {
|
|
9
9
|
coinKind,
|
|
10
|
-
formatCoinPrice
|
|
11
|
-
formatFdv
|
|
10
|
+
formatCoinPrice
|
|
12
11
|
} from "../data/coins.js";
|
|
13
12
|
const KIND_TAG = {
|
|
14
13
|
creator: "border-brand-purple/30 bg-brand-purple/10 text-brand-purple",
|
|
15
14
|
memecoin: "border-brand-rose/30 bg-brand-rose/10 text-brand-rose"
|
|
16
15
|
};
|
|
16
|
+
const KIND_GLOW = {
|
|
17
|
+
creator: "bg-brand-purple/25",
|
|
18
|
+
memecoin: "bg-brand-rose/25"
|
|
19
|
+
};
|
|
20
|
+
const KIND_FALLBACK = {
|
|
21
|
+
creator: "from-brand-blue to-brand-purple",
|
|
22
|
+
memecoin: "from-brand-purple to-brand-rose"
|
|
23
|
+
};
|
|
17
24
|
function useTileModel({ collection, usePrice }) {
|
|
18
25
|
const { price, isLoading } = usePrice(collection);
|
|
19
26
|
const kind = coinKind(collection.service);
|
|
20
|
-
const verified = collection.claimedBy != null || kind === "creator";
|
|
21
27
|
const logoUri = collection.profile?.image ?? collection.image;
|
|
22
28
|
const logo = logoUri ? ipfsToHttp(logoUri) : null;
|
|
23
29
|
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
24
|
-
const fdv = formatFdv(price?.quotePerCoin, collection.totalSupply, price?.quoteSymbol ?? null);
|
|
25
30
|
const chain = (collection.chain ?? "").toString().toLowerCase();
|
|
26
|
-
return { price, isLoading, kind,
|
|
31
|
+
return { price, isLoading, kind, logo, initials, chain };
|
|
27
32
|
}
|
|
28
|
-
function PriceSkel(
|
|
29
|
-
return /* @__PURE__ */ jsx("span", { className:
|
|
33
|
+
function PriceSkel() {
|
|
34
|
+
return /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse" });
|
|
35
|
+
}
|
|
36
|
+
function KindChip({ kind, className }) {
|
|
37
|
+
return /* @__PURE__ */ jsx("span", { className: cn("shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium", KIND_TAG[kind], className), children: kind === "creator" ? "Creator Coin" : "Memecoin" });
|
|
30
38
|
}
|
|
31
39
|
function CoinCard({ collection, usePrice, href }) {
|
|
32
40
|
const m = useTileModel({ collection, usePrice });
|
|
33
|
-
return /* @__PURE__ */ jsxs(
|
|
34
|
-
/* @__PURE__ */
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
return /* @__PURE__ */ jsxs("div", { className: "group relative h-full", children: [
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
"aria-hidden": true,
|
|
46
|
+
className: cn(
|
|
47
|
+
"pointer-events-none absolute -inset-2 rounded-[1.4rem] opacity-60 blur-xl transition-opacity duration-500 group-hover:opacity-100",
|
|
48
|
+
KIND_GLOW[m.kind]
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsx(MotionCard, { className: "card-base relative flex h-full flex-col", children: /* @__PURE__ */ jsxs(Link, { href, className: "flex h-full flex-col", children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: "relative aspect-square w-full overflow-hidden bg-muted", children: [
|
|
54
|
+
m.logo ? /* @__PURE__ */ jsx(
|
|
55
|
+
Image,
|
|
56
|
+
{
|
|
57
|
+
src: m.logo,
|
|
58
|
+
alt: collection.name ?? "Coin",
|
|
59
|
+
fill: true,
|
|
60
|
+
sizes: "(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw",
|
|
61
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
62
|
+
unoptimized: true
|
|
63
|
+
}
|
|
64
|
+
) : /* @__PURE__ */ jsx("div", { className: cn("absolute inset-0 flex items-center justify-center bg-gradient-to-br", KIND_FALLBACK[m.kind]), children: /* @__PURE__ */ jsx("span", { className: "select-none text-5xl font-black tracking-tighter text-white/90", children: m.initials }) }),
|
|
65
|
+
m.chain && /* @__PURE__ */ jsx("span", { className: "absolute bottom-2 left-2 rounded-full bg-black/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-white/80 backdrop-blur-md", children: m.chain })
|
|
42
66
|
] }),
|
|
43
|
-
/* @__PURE__ */
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
67
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 p-3", children: [
|
|
68
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
69
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
71
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsx(KindChip, { kind: m.kind })
|
|
74
|
+
] }),
|
|
75
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-auto space-y-2", children: [
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Price" }),
|
|
78
|
+
m.isLoading ? /* @__PURE__ */ jsx(PriceSkel, {}) : m.price ? /* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: formatCoinPrice(m.price.quotePerCoin) }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" })
|
|
79
|
+
] }),
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "block rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple px-3 py-2 text-center text-sm font-semibold text-white", children: "Trade" })
|
|
81
|
+
] })
|
|
82
|
+
] })
|
|
83
|
+
] }) })
|
|
57
84
|
] });
|
|
58
85
|
}
|
|
59
86
|
function CoinRow({ collection, usePrice, href }) {
|
|
60
87
|
const m = useTileModel({ collection, usePrice });
|
|
61
|
-
return /* @__PURE__ */ jsxs(Link, { href, className: "flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40", children: [
|
|
62
|
-
/* @__PURE__ */ jsx("div", { className: "relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted", children: m.logo ? /* @__PURE__ */ jsx(Image, { src: m.logo, alt: "", fill: true, sizes: "36px", className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center bg-gradient-to-br
|
|
88
|
+
return /* @__PURE__ */ jsxs(Link, { href, className: "flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40 active:scale-[0.99]", children: [
|
|
89
|
+
/* @__PURE__ */ jsx("div", { className: "relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted", children: m.logo ? /* @__PURE__ */ jsx(Image, { src: m.logo, alt: "", fill: true, sizes: "36px", className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx("div", { className: cn("flex h-full w-full items-center justify-center bg-gradient-to-br text-[11px] font-bold text-white", KIND_FALLBACK[m.kind]), children: m.initials }) }),
|
|
63
90
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
64
|
-
/* @__PURE__ */
|
|
65
|
-
|
|
66
|
-
m.verified && /* @__PURE__ */ jsx(BadgeCheck, { className: "h-3.5 w-3.5 shrink-0 text-primary", "aria-label": "Verified" })
|
|
67
|
-
] }),
|
|
68
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: collection.name ?? "Untitled coin" }),
|
|
92
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
69
93
|
] }),
|
|
70
|
-
/* @__PURE__ */ jsx(
|
|
94
|
+
/* @__PURE__ */ jsx(KindChip, { kind: m.kind, className: "hidden sm:inline-block" }),
|
|
71
95
|
/* @__PURE__ */ jsx("span", { className: "w-24 shrink-0 text-right text-sm font-semibold tabular-nums", children: m.isLoading ? /* @__PURE__ */ jsx("span", { className: "ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse" }) : m.price ? formatCoinPrice(m.price.quotePerCoin) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }),
|
|
72
96
|
/* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-sm font-medium text-primary sm:inline-block", children: "Trade" })
|
|
73
97
|
] });
|
|
74
98
|
}
|
|
75
|
-
function Stat({ label, children }) {
|
|
76
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
|
|
77
|
-
/* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: label }),
|
|
78
|
-
children
|
|
79
|
-
] });
|
|
80
|
-
}
|
|
81
99
|
function CoinCardSkeleton() {
|
|
82
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col
|
|
83
|
-
/* @__PURE__ */
|
|
84
|
-
|
|
85
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
100
|
+
return /* @__PURE__ */ jsxs("div", { className: "card-base flex flex-col", children: [
|
|
101
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-square w-full animate-pulse bg-muted" }),
|
|
102
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 p-3", children: [
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
86
104
|
/* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted-foreground/20 animate-pulse" }),
|
|
87
105
|
/* @__PURE__ */ jsx("div", { className: "h-3 w-12 rounded bg-muted-foreground/20 animate-pulse" })
|
|
88
|
-
] })
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
/* @__PURE__ */ jsx("div", { className: "h-9 w-full bg-muted-foreground/10 animate-pulse" })
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsx("div", { className: "h-9 w-full rounded-lg bg-muted-foreground/10 animate-pulse" })
|
|
108
|
+
] })
|
|
92
109
|
] });
|
|
93
110
|
}
|
|
94
111
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/coin-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinCard / CoinRow — chain-agnostic coin discovery tiles.\n *\n * Pure presentation: the price read (`usePrice`) and the link target (`href`)\n * are injected by the consuming app, and the collection is typed structurally\n * (CoinCollectionLike) — so a coin on Starknet, Ethereum, or Solana renders the\n * same with zero changes here. `chain` is shown as a badge.\n */\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { BadgeCheck, Users } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport {\n coinKind, formatCoinPrice, formatFdv,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\n\n/** Injected per-chain spot-price read. `price` is null while loading/unknown. */\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport interface CoinTileProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n /** Link target — internal coin page or the per-chain trading app. */\n href: string;\n}\n\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n memecoin: \"border-brand-rose/30 bg-brand-rose/10 text-brand-rose\",\n};\n\nfunction useTileModel({ collection, usePrice }: Omit<CoinTileProps, \"href\">) {\n const { price, isLoading } = usePrice(collection);\n const kind = coinKind(collection.service);\n const verified = collection.claimedBy != null || kind === \"creator\";\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const fdv = formatFdv(price?.quotePerCoin, collection.totalSupply, price?.quoteSymbol ?? null);\n const chain = (collection.chain ?? \"\").toString().toLowerCase();\n return { price, isLoading, kind, verified, logo, initials, fdv, chain };\n}\n\nfunction PriceSkel({ wide }: { wide?: boolean }) {\n return <span className={cn(\"inline-block h-4 rounded bg-muted-foreground/20 animate-pulse\", wide ? \"w-16\" : \"w-12\")} />;\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex flex-col rounded-xl border border-border/60 bg-card overflow-hidden transition-transform active:scale-[0.99]\">\n <div className=\"flex items-center gap-3 p-4\">\n <div className=\"relative h-12 w-12 shrink-0 rounded-full overflow-hidden bg-muted\">\n {m.logo ? (\n <Image src={m.logo} alt=\"\" fill sizes=\"48px\" className=\"object-cover\" unoptimized />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-brand-blue to-brand-purple text-sm font-bold text-white\">{m.initials}</div>\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n {m.verified && <BadgeCheck className=\"h-4 w-4 shrink-0 text-primary\" aria-label=\"Verified\" />}\n </div>\n <span className=\"text-sm text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <span className={cn(\"shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium\", KIND_TAG[m.kind])}>\n {m.kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n </div>\n <div className=\"grid grid-cols-3 gap-2 border-t border-border/60 px-4 py-3 text-sm\">\n <Stat label=\"Price\">\n {m.isLoading ? <PriceSkel /> : m.price ? <span className=\"font-semibold\">{formatCoinPrice(m.price.quotePerCoin)}</span> : <span className=\"text-muted-foreground\">—</span>}\n </Stat>\n <Stat label=\"FDV\">\n {m.isLoading ? <PriceSkel /> : <span className=\"font-semibold\">{m.fdv ?? \"—\"}</span>}\n </Stat>\n <Stat label=\"Holders\">\n <span className=\"inline-flex items-center gap-1 font-semibold\">\n <Users className=\"h-3 w-3 text-muted-foreground\" />{collection.holderCount || \"—\"}\n </span>\n </Stat>\n </div>\n <div className=\"flex items-center justify-between border-t border-border/60 px-4 py-2.5 text-sm\">\n {m.chain ? <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{m.chain}</span> : <span />}\n <span className=\"font-medium text-primary\">Trade</span>\n </div>\n </Link>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40\">\n <div className=\"relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted\">\n {m.logo ? <Image src={m.logo} alt=\"\" fill sizes=\"36px\" className=\"object-cover\" unoptimized /> :\n <div className=\"flex h-full w-full items-center justify-center bg-gradient-to-br from-brand-blue to-brand-purple text-[11px] font-bold text-white\">{m.initials}</div>}\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"truncate text-sm font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n {m.verified && <BadgeCheck className=\"h-3.5 w-3.5 shrink-0 text-primary\" aria-label=\"Verified\" />}\n </div>\n <span className=\"text-xs text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <span className={cn(\"hidden shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium sm:inline-block\", KIND_TAG[m.kind])}>\n {m.kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n <span className=\"w-24 shrink-0 text-right text-sm font-semibold tabular-nums\">\n {m.isLoading ? <span className=\"ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse\" /> : m.price ? formatCoinPrice(m.price.quotePerCoin) : <span className=\"text-muted-foreground\">—</span>}\n </span>\n <span className=\"hidden shrink-0 text-sm font-medium text-primary sm:inline-block\">Trade</span>\n </Link>\n );\n}\n\nfunction Stat({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"flex flex-col gap-0.5\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{label}</span>\n {children}\n </div>\n );\n}\n\nexport function CoinCardSkeleton() {\n return (\n <div className=\"flex flex-col rounded-xl border border-border/60 bg-card overflow-hidden\">\n <div className=\"flex items-center gap-3 p-4\">\n <div className=\"h-12 w-12 rounded-full bg-muted animate-pulse\" />\n <div className=\"flex-1 space-y-1.5\">\n <div className=\"h-4 w-24 rounded bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-12 rounded bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n <div className=\"grid grid-cols-3 gap-2 border-t border-border/60 px-4 py-3\">\n {[0, 1, 2].map((i) => <div key={i} className=\"h-8 w-full rounded bg-muted-foreground/10 animate-pulse\" />)}\n </div>\n <div className=\"h-9 w-full bg-muted-foreground/10 animate-pulse\" />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/coin-card.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinCard / CoinRow — chain-agnostic, art-forward coin discovery tiles.\n *\n * Pure presentation: the price read (`usePrice`) and the link target (`href`)\n * are injected by the consuming app, and the collection is typed structurally\n * (CoinCollectionLike) — so a coin on Starknet, Ethereum, or Solana renders the\n * same with zero changes here. The coin's artwork is the hero; the only stat is\n * the live spot price (FDV / holders / a \"verified\" mark are intentionally not\n * shown — Medialane indexes none of them).\n */\n\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport {\n coinKind, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\n\n/** Injected per-chain spot-price read. `price` is null while loading/unknown. */\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport interface CoinTileProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n /** Link target — internal coin page or the per-chain trading app. */\n href: string;\n}\n\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n memecoin: \"border-brand-rose/30 bg-brand-rose/10 text-brand-rose\",\n};\n\n// Aurora glow + art-fallback gradient, tinted by kind (foundations §III: rose\n// is the coin surface, purple/blue the creator accent).\nconst KIND_GLOW: Record<string, string> = {\n creator: \"bg-brand-purple/25\",\n memecoin: \"bg-brand-rose/25\",\n};\nconst KIND_FALLBACK: Record<string, string> = {\n creator: \"from-brand-blue to-brand-purple\",\n memecoin: \"from-brand-purple to-brand-rose\",\n};\n\nfunction useTileModel({ collection, usePrice }: Omit<CoinTileProps, \"href\">) {\n const { price, isLoading } = usePrice(collection);\n const kind = coinKind(collection.service);\n const logoUri = collection.profile?.image ?? collection.image;\n const logo = logoUri ? ipfsToHttp(logoUri) : null;\n const initials = (collection.symbol ?? collection.name ?? \"?\").trim().slice(0, 2).toUpperCase();\n const chain = (collection.chain ?? \"\").toString().toLowerCase();\n return { price, isLoading, kind, logo, initials, chain };\n}\n\nfunction PriceSkel() {\n return <span className=\"inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse\" />;\n}\n\nfunction KindChip({ kind, className }: { kind: string; className?: string }) {\n return (\n <span className={cn(\"shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium\", KIND_TAG[kind], className)}>\n {kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\"}\n </span>\n );\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <div className=\"group relative h-full\">\n {/* Aurora glow — soft, kind-tinted, blooms past the card edge. */}\n <div\n aria-hidden\n className={cn(\n \"pointer-events-none absolute -inset-2 rounded-[1.4rem] opacity-60 blur-xl transition-opacity duration-500 group-hover:opacity-100\",\n KIND_GLOW[m.kind]\n )}\n />\n <MotionCard className=\"card-base relative flex h-full flex-col\">\n <Link href={href} className=\"flex h-full flex-col\">\n {/* Hero artwork */}\n <div className=\"relative aspect-square w-full overflow-hidden bg-muted\">\n {m.logo ? (\n <Image\n src={m.logo}\n alt={collection.name ?? \"Coin\"}\n fill\n sizes=\"(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw\"\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n unoptimized\n />\n ) : (\n <div className={cn(\"absolute inset-0 flex items-center justify-center bg-gradient-to-br\", KIND_FALLBACK[m.kind])}>\n <span className=\"select-none text-5xl font-black tracking-tighter text-white/90\">{m.initials}</span>\n </div>\n )}\n {m.chain && (\n <span className=\"absolute bottom-2 left-2 rounded-full bg-black/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-white/80 backdrop-blur-md\">\n {m.chain}\n </span>\n )}\n </div>\n\n {/* Body */}\n <div className=\"flex flex-1 flex-col gap-3 p-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <div className=\"truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</div>\n <div className=\"truncate text-sm text-muted-foreground\">{collection.symbol ?? \"—\"}</div>\n </div>\n <KindChip kind={m.kind} />\n </div>\n <div className=\"mt-auto space-y-2\">\n <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Price</span>\n {m.isLoading ? <PriceSkel /> : m.price ? (\n <span className=\"font-semibold tabular-nums\">{formatCoinPrice(m.price.quotePerCoin)}</span>\n ) : (\n <span className=\"text-muted-foreground\">—</span>\n )}\n </div>\n <span className=\"block rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple px-3 py-2 text-center text-sm font-semibold text-white\">\n Trade\n </span>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <Link href={href} className=\"flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-primary/40 active:scale-[0.99]\">\n <div className=\"relative h-9 w-9 shrink-0 overflow-hidden rounded-full bg-muted\">\n {m.logo ? <Image src={m.logo} alt=\"\" fill sizes=\"36px\" className=\"object-cover\" unoptimized /> :\n <div className={cn(\"flex h-full w-full items-center justify-center bg-gradient-to-br text-[11px] font-bold text-white\", KIND_FALLBACK[m.kind])}>{m.initials}</div>}\n </div>\n <div className=\"min-w-0 flex-1\">\n <span className=\"truncate text-sm font-semibold\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">{collection.symbol ?? \"—\"}</span>\n </div>\n <KindChip kind={m.kind} className=\"hidden sm:inline-block\" />\n <span className=\"w-24 shrink-0 text-right text-sm font-semibold tabular-nums\">\n {m.isLoading ? <span className=\"ml-auto inline-block h-4 w-12 rounded bg-muted-foreground/20 animate-pulse\" /> : m.price ? formatCoinPrice(m.price.quotePerCoin) : <span className=\"text-muted-foreground\">—</span>}\n </span>\n <span className=\"hidden shrink-0 text-sm font-medium text-primary sm:inline-block\">Trade</span>\n </Link>\n );\n}\n\nexport function CoinCardSkeleton() {\n return (\n <div className=\"card-base flex flex-col\">\n <div className=\"aspect-square w-full animate-pulse bg-muted\" />\n <div className=\"flex flex-col gap-3 p-3\">\n <div className=\"space-y-1.5\">\n <div className=\"h-4 w-24 rounded bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-12 rounded bg-muted-foreground/20 animate-pulse\" />\n </div>\n <div className=\"h-9 w-full rounded-lg bg-muted-foreground/10 animate-pulse\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -21,7 +21,10 @@ __export(license_summary_exports, {
|
|
|
21
21
|
licenseSummary: () => licenseSummary
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(license_summary_exports);
|
|
24
|
-
const get = (a, key) =>
|
|
24
|
+
const get = (a, key) => {
|
|
25
|
+
const v = a.find((x) => x.trait_type?.toLowerCase() === key)?.value;
|
|
26
|
+
return v == null ? void 0 : String(v);
|
|
27
|
+
};
|
|
25
28
|
function licenseSummary(attributes) {
|
|
26
29
|
const commercialRaw = get(attributes, "commercial use");
|
|
27
30
|
const derivatives = (get(attributes, "derivatives") ?? "").toLowerCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/license-summary.ts"],"sourcesContent":["type Attr = { trait_type?: string; value?: string };\n\nconst get = (a: Attr[], key: string): string | undefined
|
|
1
|
+
{"version":3,"sources":["../../src/utils/license-summary.ts"],"sourcesContent":["type Attr = { trait_type?: string; value?: string };\n\nconst get = (a: Attr[], key: string): string | undefined => {\n // Attribute values can be numbers at runtime (OpenSea allows numeric `value`s);\n // coerce so the callers' `.toLowerCase()` never throws.\n const v = a.find((x) => x.trait_type?.toLowerCase() === key)?.value;\n return v == null ? undefined : String(v);\n};\n\n/**\n * One plain-language sentence summarizing the license, derived from the license\n * traits. Deliberately omits attribution/credit wording (not the place for it).\n * Returns null when there's no license data to summarize.\n *\n * Trait keys match the asset metadata: \"Commercial Use\", \"Derivatives\",\n * \"AI Policy\", \"Territory\".\n */\nexport function licenseSummary(attributes: Attr[]): string | null {\n const commercialRaw = get(attributes, \"commercial use\");\n const derivatives = (get(attributes, \"derivatives\") ?? \"\").toLowerCase();\n const ai = (get(attributes, \"ai policy\") ?? get(attributes, \"ai & data mining\") ?? \"\").toLowerCase();\n const territory = get(attributes, \"territory\") ?? \"\";\n\n if (!commercialRaw && !derivatives && !ai) return null;\n\n const commercial = (commercialRaw ?? \"\").toLowerCase() === \"yes\";\n const remix =\n derivatives === \"not allowed\"\n ? \"No remixing\"\n : commercial\n ? \"Open to remix and commercial use\"\n : \"Open to remix\";\n const where = territory ? `, ${territory.toLowerCase()}` : \"\";\n const aiClause = ai.includes(\"not\") ? \" — no AI training\" : \"\";\n return `${remix}${where}${aiClause}.`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,MAAM,MAAM,CAAC,GAAW,QAAoC;AAG1D,QAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY,MAAM,GAAG,GAAG;AAC9D,SAAO,KAAK,OAAO,SAAY,OAAO,CAAC;AACzC;AAUO,SAAS,eAAe,YAAmC;AAChE,QAAM,gBAAgB,IAAI,YAAY,gBAAgB;AACtD,QAAM,eAAe,IAAI,YAAY,aAAa,KAAK,IAAI,YAAY;AACvE,QAAM,MAAM,IAAI,YAAY,WAAW,KAAK,IAAI,YAAY,kBAAkB,KAAK,IAAI,YAAY;AACnG,QAAM,YAAY,IAAI,YAAY,WAAW,KAAK;AAElD,MAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,GAAI,QAAO;AAElD,QAAM,cAAc,iBAAiB,IAAI,YAAY,MAAM;AAC3D,QAAM,QACJ,gBAAgB,gBACZ,gBACA,aACE,qCACA;AACR,QAAM,QAAQ,YAAY,KAAK,UAAU,YAAY,CAAC,KAAK;AAC3D,QAAM,WAAW,GAAG,SAAS,KAAK,IAAI,2BAAsB;AAC5D,SAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ;AACpC;","names":[]}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
const get = (a, key) =>
|
|
1
|
+
const get = (a, key) => {
|
|
2
|
+
const v = a.find((x) => x.trait_type?.toLowerCase() === key)?.value;
|
|
3
|
+
return v == null ? void 0 : String(v);
|
|
4
|
+
};
|
|
2
5
|
function licenseSummary(attributes) {
|
|
3
6
|
const commercialRaw = get(attributes, "commercial use");
|
|
4
7
|
const derivatives = (get(attributes, "derivatives") ?? "").toLowerCase();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/license-summary.ts"],"sourcesContent":["type Attr = { trait_type?: string; value?: string };\n\nconst get = (a: Attr[], key: string): string | undefined
|
|
1
|
+
{"version":3,"sources":["../../src/utils/license-summary.ts"],"sourcesContent":["type Attr = { trait_type?: string; value?: string };\n\nconst get = (a: Attr[], key: string): string | undefined => {\n // Attribute values can be numbers at runtime (OpenSea allows numeric `value`s);\n // coerce so the callers' `.toLowerCase()` never throws.\n const v = a.find((x) => x.trait_type?.toLowerCase() === key)?.value;\n return v == null ? undefined : String(v);\n};\n\n/**\n * One plain-language sentence summarizing the license, derived from the license\n * traits. Deliberately omits attribution/credit wording (not the place for it).\n * Returns null when there's no license data to summarize.\n *\n * Trait keys match the asset metadata: \"Commercial Use\", \"Derivatives\",\n * \"AI Policy\", \"Territory\".\n */\nexport function licenseSummary(attributes: Attr[]): string | null {\n const commercialRaw = get(attributes, \"commercial use\");\n const derivatives = (get(attributes, \"derivatives\") ?? \"\").toLowerCase();\n const ai = (get(attributes, \"ai policy\") ?? get(attributes, \"ai & data mining\") ?? \"\").toLowerCase();\n const territory = get(attributes, \"territory\") ?? \"\";\n\n if (!commercialRaw && !derivatives && !ai) return null;\n\n const commercial = (commercialRaw ?? \"\").toLowerCase() === \"yes\";\n const remix =\n derivatives === \"not allowed\"\n ? \"No remixing\"\n : commercial\n ? \"Open to remix and commercial use\"\n : \"Open to remix\";\n const where = territory ? `, ${territory.toLowerCase()}` : \"\";\n const aiClause = ai.includes(\"not\") ? \" — no AI training\" : \"\";\n return `${remix}${where}${aiClause}.`;\n}\n"],"mappings":"AAEA,MAAM,MAAM,CAAC,GAAW,QAAoC;AAG1D,QAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,YAAY,YAAY,MAAM,GAAG,GAAG;AAC9D,SAAO,KAAK,OAAO,SAAY,OAAO,CAAC;AACzC;AAUO,SAAS,eAAe,YAAmC;AAChE,QAAM,gBAAgB,IAAI,YAAY,gBAAgB;AACtD,QAAM,eAAe,IAAI,YAAY,aAAa,KAAK,IAAI,YAAY;AACvE,QAAM,MAAM,IAAI,YAAY,WAAW,KAAK,IAAI,YAAY,kBAAkB,KAAK,IAAI,YAAY;AACnG,QAAM,YAAY,IAAI,YAAY,WAAW,KAAK;AAElD,MAAI,CAAC,iBAAiB,CAAC,eAAe,CAAC,GAAI,QAAO;AAElD,QAAM,cAAc,iBAAiB,IAAI,YAAY,MAAM;AAC3D,QAAM,QACJ,gBAAgB,gBACZ,gBACA,aACE,qCACA;AACR,QAAM,QAAQ,YAAY,KAAK,UAAU,YAAY,CAAC,KAAK;AAC3D,QAAM,WAAW,GAAG,SAAS,KAAK,IAAI,2BAAsB;AAC5D,SAAO,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ;AACpC;","names":[]}
|