@medialane/ui 0.24.0 → 0.26.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/README.md +3 -0
- package/dist/components/coin-card.cjs +43 -59
- package/dist/components/coin-card.cjs.map +1 -1
- package/dist/components/coin-card.js +43 -59
- package/dist/components/coin-card.js.map +1 -1
- package/dist/components/coins-explorer.cjs +106 -42
- package/dist/components/coins-explorer.cjs.map +1 -1
- package/dist/components/coins-explorer.js +108 -44
- package/dist/components/coins-explorer.js.map +1 -1
- package/dist/components/launchpad-services.cjs +111 -67
- package/dist/components/launchpad-services.cjs.map +1 -1
- package/dist/components/launchpad-services.d.cts +3 -1
- package/dist/components/launchpad-services.d.ts +3 -1
- package/dist/components/launchpad-services.js +113 -69
- package/dist/components/launchpad-services.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -233,6 +233,9 @@ The package uses [tsup](https://tsup.egoist.dev/) and outputs ESM + CJS + type d
|
|
|
233
233
|
|
|
234
234
|
| Version | Added |
|
|
235
235
|
|---|---|
|
|
236
|
+
| **v0.26.0** | `LaunchpadServiceCard` "living color cards" high-fidelity pass: per-hue aurora light-leaks, gradient icon tile, hairline gradient frame that ignites on interaction, staggered entrance reveal, press/hover microinteractions, animated CTA arrow; roomier grid gaps + section rhythm. Touch-first, reduced-motion safe |
|
|
237
|
+
| **v0.25.0** | `CoinsExplorer`/`CoinCard` refined: kind label over the artwork (brand hues, not red), quote-currency icon on price, marketplace-style Filters dialog; dropped the Verified badge / FDV / holders / per-card glow / Trade button |
|
|
238
|
+
| **v0.24.0** | Art-forward `CoinCard` redesign — the coin's cover artwork becomes the hero of the tile |
|
|
236
239
|
| **v0.14.0** | `docUpload` template config + `DOC_UPLOAD` (document/PDF → IPFS for Documents/Patents/Publications/Software), `IPTypeDisplay` document card |
|
|
237
240
|
| **v0.13.x** | Asset-page modules lifted: `AssetOverviewContent`, `AssetMarketsTab`, `AssetMediaColumn`/`AssetHeaderBlock`, `ParentAttributionBanner`, `IPTypeDisplay`; IP data layer (`data/ip`, `data/ip-templates`); `timeUntil` |
|
|
238
241
|
| **v0.12.x** | `LaunchpadStrip` (homepage carousel from service defs); Discover strips restyled to the approved design; `CollectionCard` gated-content border + currency floor |
|
|
@@ -40,80 +40,65 @@ var import_image = __toESM(require("next/image"), 1);
|
|
|
40
40
|
var import_cn = require("../utils/cn.js");
|
|
41
41
|
var import_ipfs = require("../utils/ipfs.js");
|
|
42
42
|
var import_motion_primitives = require("./motion-primitives.js");
|
|
43
|
+
var import_currency_icon = require("./currency-icon.js");
|
|
43
44
|
var import_coins = require("../data/coins.js");
|
|
44
45
|
const KIND_TAG = {
|
|
45
|
-
creator: "border-brand-
|
|
46
|
-
memecoin: "border-brand-
|
|
46
|
+
creator: "border-brand-blue/30 bg-brand-blue/10 text-brand-blue",
|
|
47
|
+
memecoin: "border-brand-purple/30 bg-brand-purple/10 text-brand-purple"
|
|
47
48
|
};
|
|
48
|
-
const
|
|
49
|
-
creator: "bg-brand-
|
|
50
|
-
memecoin: "bg-brand-
|
|
49
|
+
const KIND_OVER_IMAGE = {
|
|
50
|
+
creator: "bg-brand-blue/85 text-white",
|
|
51
|
+
memecoin: "bg-brand-purple/85 text-white"
|
|
51
52
|
};
|
|
52
53
|
const KIND_FALLBACK = {
|
|
53
54
|
creator: "from-brand-blue to-brand-purple",
|
|
54
55
|
memecoin: "from-brand-purple to-brand-rose"
|
|
55
56
|
};
|
|
57
|
+
const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
|
|
56
58
|
function useTileModel({ collection, usePrice }) {
|
|
57
59
|
const { price, isLoading } = usePrice(collection);
|
|
58
60
|
const kind = (0, import_coins.coinKind)(collection.service);
|
|
59
61
|
const logoUri = collection.profile?.image ?? collection.image;
|
|
60
62
|
const logo = logoUri ? (0, import_ipfs.ipfsToHttp)(logoUri) : null;
|
|
61
63
|
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
62
|
-
|
|
63
|
-
return { price, isLoading, kind, logo, initials, chain };
|
|
64
|
+
return { price, isLoading, kind, logo, initials };
|
|
64
65
|
}
|
|
65
|
-
function
|
|
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
|
-
|
|
69
|
-
|
|
66
|
+
function PriceValue({ price, isLoading }) {
|
|
67
|
+
if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse" });
|
|
68
|
+
if (!price) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
69
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1 font-semibold tabular-nums", children: [
|
|
70
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: price.quoteSymbol, size: 14 }),
|
|
71
|
+
(0, import_coins.formatCoinPrice)(price.quotePerCoin)
|
|
72
|
+
] });
|
|
70
73
|
}
|
|
71
74
|
function CoinCard({ collection, usePrice, href }) {
|
|
72
75
|
const m = useTileModel({ collection, usePrice });
|
|
73
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.
|
|
74
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 })
|
|
76
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_motion_primitives.MotionCard, { className: "card-base group relative flex h-full flex-col", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_link.default, { href, className: "flex h-full flex-col", children: [
|
|
77
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-square w-full overflow-hidden bg-muted", children: [
|
|
78
|
+
m.logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
79
|
+
import_image.default,
|
|
80
|
+
{
|
|
81
|
+
src: m.logo,
|
|
82
|
+
alt: collection.name ?? "Coin",
|
|
83
|
+
fill: true,
|
|
84
|
+
sizes: "(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw",
|
|
85
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
86
|
+
unoptimized: true
|
|
87
|
+
}
|
|
88
|
+
) : /* @__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 }) }),
|
|
89
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("absolute left-2 top-2 rounded-full px-2 py-0.5 text-[10px] font-semibold backdrop-blur-md", KIND_OVER_IMAGE[m.kind]), children: kindLabel(m.kind) })
|
|
90
|
+
] }),
|
|
91
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2 p-3", children: [
|
|
92
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
|
|
93
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
94
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "truncate text-sm text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
98
95
|
] }),
|
|
99
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex
|
|
100
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
101
|
-
|
|
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
|
-
] })
|
|
96
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
97
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Price" }),
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceValue, { price: m.price, isLoading: m.isLoading })
|
|
114
99
|
] })
|
|
115
|
-
] })
|
|
116
|
-
] });
|
|
100
|
+
] })
|
|
101
|
+
] }) });
|
|
117
102
|
}
|
|
118
103
|
function CoinRow({ collection, usePrice, href }) {
|
|
119
104
|
const m = useTileModel({ collection, usePrice });
|
|
@@ -123,20 +108,19 @@ function CoinRow({ collection, usePrice, href }) {
|
|
|
123
108
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "truncate text-sm font-semibold", children: collection.name ?? "Untitled coin" }),
|
|
124
109
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block truncate text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
125
110
|
] }),
|
|
126
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
127
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-
|
|
128
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hidden shrink-0 text-sm font-medium text-primary sm:inline-block", children: "Trade" })
|
|
111
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: (0, import_cn.cn)("hidden shrink-0 rounded-full border px-2 py-0.5 text-[10px] font-medium sm:inline-block", KIND_TAG[m.kind]), children: kindLabel(m.kind) }),
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "w-28 shrink-0 text-right text-sm", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceValue, { price: m.price, isLoading: m.isLoading }) })
|
|
129
113
|
] });
|
|
130
114
|
}
|
|
131
115
|
function CoinCardSkeleton() {
|
|
132
116
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "card-base flex flex-col", children: [
|
|
133
117
|
/* @__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-
|
|
118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col gap-2 p-3", children: [
|
|
135
119
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-1.5", children: [
|
|
136
120
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-4 w-24 rounded bg-muted-foreground/20 animate-pulse" }),
|
|
137
121
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-3 w-12 rounded bg-muted-foreground/20 animate-pulse" })
|
|
138
122
|
] }),
|
|
139
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-
|
|
123
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-4 w-full rounded bg-muted-foreground/10 animate-pulse" })
|
|
140
124
|
] })
|
|
141
125
|
] });
|
|
142
126
|
}
|
|
@@ -1 +1 @@
|
|
|
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"]}
|
|
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 whole tile\n * links to the coin page (where trading happens), and the only stat is the live\n * spot price with its quote-currency icon. FDV / holders / a \"verified\" mark are\n * intentionally not 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 { CurrencyIcon } from \"./currency-icon.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\n// Kind tag on a card surface (table row) — brand hues, never red.\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-blue/30 bg-brand-blue/10 text-brand-blue\",\n memecoin: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n};\n// Kind tag layered over artwork — solid brand pill, white text for legibility.\nconst KIND_OVER_IMAGE: Record<string, string> = {\n creator: \"bg-brand-blue/85 text-white\",\n memecoin: \"bg-brand-purple/85 text-white\",\n};\n// Art-fallback gradient (no logo) — vivid brand wash.\nconst KIND_FALLBACK: Record<string, string> = {\n creator: \"from-brand-blue to-brand-purple\",\n memecoin: \"from-brand-purple to-brand-rose\",\n};\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\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 return { price, isLoading, kind, logo, initials };\n}\n\nfunction PriceValue({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse\" />;\n if (!price) return <span className=\"text-muted-foreground\">—</span>;\n return (\n <span className=\"inline-flex items-center gap-1 font-semibold tabular-nums\">\n <CurrencyIcon symbol={price.quoteSymbol} size={14} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n );\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <MotionCard className=\"card-base group 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 <span className={cn(\"absolute left-2 top-2 rounded-full px-2 py-0.5 text-[10px] font-semibold backdrop-blur-md\", KIND_OVER_IMAGE[m.kind])}>\n {kindLabel(m.kind)}\n </span>\n </div>\n\n {/* Body */}\n <div className=\"flex flex-col gap-2 p-3\">\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 <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Price</span>\n <PriceValue price={m.price} isLoading={m.isLoading} />\n </div>\n </div>\n </Link>\n </MotionCard>\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 <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 {kindLabel(m.kind)}\n </span>\n <span className=\"w-28 shrink-0 text-right text-sm\">\n <PriceValue price={m.price} isLoading={m.isLoading} />\n </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-2 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-4 w-full rounded bg-muted-foreground/10 animate-pulse\" />\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAkEwB;AApDxB,kBAAiB;AACjB,mBAAkB;AAClB,gBAAmB;AACnB,kBAA2B;AAC3B,+BAA2B;AAC3B,2BAA6B;AAC7B,mBAGO;AAgBP,MAAM,WAAmC;AAAA,EACvC,SAAS;AAAA,EACT,UAAU;AACZ;AAEA,MAAM,kBAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,UAAU;AACZ;AAEA,MAAM,gBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,UAAU;AACZ;AAEA,MAAM,YAAY,CAAC,SAAkB,SAAS,YAAY,iBAAiB;AAE3E,SAAS,aAAa,EAAE,YAAY,SAAS,GAAgC;AAC3E,QAAM,EAAE,OAAO,UAAU,IAAI,SAAS,UAAU;AAChD,QAAM,WAAO,uBAAS,WAAW,OAAO;AACxC,QAAM,UAAU,WAAW,SAAS,SAAS,WAAW;AACxD,QAAM,OAAO,cAAU,wBAAW,OAAO,IAAI;AAC7C,QAAM,YAAY,WAAW,UAAU,WAAW,QAAQ,KAAK,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,YAAY;AAC9F,SAAO,EAAE,OAAO,WAAW,MAAM,MAAM,SAAS;AAClD;AAEA,SAAS,WAAW,EAAE,OAAO,UAAU,GAAwD;AAC7F,MAAI,UAAW,QAAO,4CAAC,UAAK,WAAU,sEAAqE;AAC3G,MAAI,CAAC,MAAO,QAAO,4CAAC,UAAK,WAAU,yBAAwB,oBAAC;AAC5D,SACE,6CAAC,UAAK,WAAU,6DACd;AAAA,gDAAC,qCAAa,QAAQ,MAAM,aAAa,MAAM,IAAI;AAAA,QAClD,8BAAgB,MAAM,YAAY;AAAA,KACrC;AAEJ;AAEO,SAAS,SAAS,EAAE,YAAY,UAAU,KAAK,GAAkB;AACtE,QAAM,IAAI,aAAa,EAAE,YAAY,SAAS,CAAC;AAC/C,SACE,4CAAC,uCAAW,WAAU,iDACpB,uDAAC,YAAAA,SAAA,EAAK,MAAY,WAAU,wBAE1B;AAAA,iDAAC,SAAI,WAAU,0DACZ;AAAA,QAAE,OACD;AAAA,QAAC,aAAAC;AAAA,QAAA;AAAA,UACC,KAAK,EAAE;AAAA,UACP,KAAK,WAAW,QAAQ;AAAA,UACxB,MAAI;AAAA,UACJ,OAAM;AAAA,UACN,WAAU;AAAA,UACV,aAAW;AAAA;AAAA,MACb,IAEA,4CAAC,SAAI,eAAW,cAAG,uEAAuE,cAAc,EAAE,IAAI,CAAC,GAC7G,sDAAC,UAAK,WAAU,kEAAkE,YAAE,UAAS,GAC/F;AAAA,MAEF,4CAAC,UAAK,eAAW,cAAG,6FAA6F,gBAAgB,EAAE,IAAI,CAAC,GACrI,oBAAU,EAAE,IAAI,GACnB;AAAA,OACF;AAAA,IAGA,6CAAC,SAAI,WAAU,2BACb;AAAA,mDAAC,SAAI,WAAU,WACb;AAAA,oDAAC,SAAI,WAAU,wCAAwC,qBAAW,QAAQ,iBAAgB;AAAA,QAC1F,4CAAC,SAAI,WAAU,0CAA0C,qBAAW,UAAU,UAAI;AAAA,SACpF;AAAA,MACA,6CAAC,SAAI,WAAU,6CACb;AAAA,oDAAC,UAAK,WAAU,6DAA4D,mBAAK;AAAA,QACjF,4CAAC,cAAW,OAAO,EAAE,OAAO,WAAW,EAAE,WAAW;AAAA,SACtD;AAAA,OACF;AAAA,KACF,GACF;AAEJ;AAEO,SAAS,QAAQ,EAAE,YAAY,UAAU,KAAK,GAAkB;AACrE,QAAM,IAAI,aAAa,EAAE,YAAY,SAAS,CAAC;AAC/C,SACE,6CAAC,YAAAD,SAAA,EAAK,MAAY,WAAU,gJAC1B;AAAA,gDAAC,SAAI,WAAU,mEACZ,YAAE,OAAO,4CAAC,aAAAC,SAAA,EAAM,KAAK,EAAE,MAAM,KAAI,IAAG,MAAI,MAAC,OAAM,QAAO,WAAU,gBAAe,aAAW,MAAC,IAC1F,4CAAC,SAAI,eAAW,cAAG,qGAAqG,cAAc,EAAE,IAAI,CAAC,GAAI,YAAE,UAAS,GAChK;AAAA,IACA,6CAAC,SAAI,WAAU,kBACb;AAAA,kDAAC,UAAK,WAAU,kCAAkC,qBAAW,QAAQ,iBAAgB;AAAA,MACrF,4CAAC,UAAK,WAAU,gDAAgD,qBAAW,UAAU,UAAI;AAAA,OAC3F;AAAA,IACA,4CAAC,UAAK,eAAW,cAAG,2FAA2F,SAAS,EAAE,IAAI,CAAC,GAC5H,oBAAU,EAAE,IAAI,GACnB;AAAA,IACA,4CAAC,UAAK,WAAU,oCACd,sDAAC,cAAW,OAAO,EAAE,OAAO,WAAW,EAAE,WAAW,GACtD;AAAA,KACF;AAEJ;AAEO,SAAS,mBAAmB;AACjC,SACE,6CAAC,SAAI,WAAU,2BACb;AAAA,gDAAC,SAAI,WAAU,+CAA8C;AAAA,IAC7D,6CAAC,SAAI,WAAU,2BACb;AAAA,mDAAC,SAAI,WAAU,eACb;AAAA,oDAAC,SAAI,WAAU,yDAAwD;AAAA,QACvE,4CAAC,SAAI,WAAU,yDAAwD;AAAA,SACzE;AAAA,MACA,4CAAC,SAAI,WAAU,2DAA0D;AAAA,OAC3E;AAAA,KACF;AAEJ;","names":["Link","Image"]}
|
|
@@ -5,83 +5,68 @@ import Image from "next/image";
|
|
|
5
5
|
import { cn } from "../utils/cn.js";
|
|
6
6
|
import { ipfsToHttp } from "../utils/ipfs.js";
|
|
7
7
|
import { MotionCard } from "./motion-primitives.js";
|
|
8
|
+
import { CurrencyIcon } from "./currency-icon.js";
|
|
8
9
|
import {
|
|
9
10
|
coinKind,
|
|
10
11
|
formatCoinPrice
|
|
11
12
|
} from "../data/coins.js";
|
|
12
13
|
const KIND_TAG = {
|
|
13
|
-
creator: "border-brand-
|
|
14
|
-
memecoin: "border-brand-
|
|
14
|
+
creator: "border-brand-blue/30 bg-brand-blue/10 text-brand-blue",
|
|
15
|
+
memecoin: "border-brand-purple/30 bg-brand-purple/10 text-brand-purple"
|
|
15
16
|
};
|
|
16
|
-
const
|
|
17
|
-
creator: "bg-brand-
|
|
18
|
-
memecoin: "bg-brand-
|
|
17
|
+
const KIND_OVER_IMAGE = {
|
|
18
|
+
creator: "bg-brand-blue/85 text-white",
|
|
19
|
+
memecoin: "bg-brand-purple/85 text-white"
|
|
19
20
|
};
|
|
20
21
|
const KIND_FALLBACK = {
|
|
21
22
|
creator: "from-brand-blue to-brand-purple",
|
|
22
23
|
memecoin: "from-brand-purple to-brand-rose"
|
|
23
24
|
};
|
|
25
|
+
const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
|
|
24
26
|
function useTileModel({ collection, usePrice }) {
|
|
25
27
|
const { price, isLoading } = usePrice(collection);
|
|
26
28
|
const kind = coinKind(collection.service);
|
|
27
29
|
const logoUri = collection.profile?.image ?? collection.image;
|
|
28
30
|
const logo = logoUri ? ipfsToHttp(logoUri) : null;
|
|
29
31
|
const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
|
|
30
|
-
|
|
31
|
-
return { price, isLoading, kind, logo, initials, chain };
|
|
32
|
+
return { price, isLoading, kind, logo, initials };
|
|
32
33
|
}
|
|
33
|
-
function
|
|
34
|
-
return /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse" });
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
function PriceValue({ price, isLoading }) {
|
|
35
|
+
if (isLoading) return /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse" });
|
|
36
|
+
if (!price) return /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
37
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 font-semibold tabular-nums", children: [
|
|
38
|
+
/* @__PURE__ */ jsx(CurrencyIcon, { symbol: price.quoteSymbol, size: 14 }),
|
|
39
|
+
formatCoinPrice(price.quotePerCoin)
|
|
40
|
+
] });
|
|
38
41
|
}
|
|
39
42
|
function CoinCard({ collection, usePrice, href }) {
|
|
40
43
|
const m = useTileModel({ collection, usePrice });
|
|
41
|
-
return /* @__PURE__ */
|
|
42
|
-
/* @__PURE__ */
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 })
|
|
44
|
+
return /* @__PURE__ */ jsx(MotionCard, { className: "card-base group relative flex h-full flex-col", children: /* @__PURE__ */ jsxs(Link, { href, className: "flex h-full flex-col", children: [
|
|
45
|
+
/* @__PURE__ */ jsxs("div", { className: "relative aspect-square w-full overflow-hidden bg-muted", children: [
|
|
46
|
+
m.logo ? /* @__PURE__ */ jsx(
|
|
47
|
+
Image,
|
|
48
|
+
{
|
|
49
|
+
src: m.logo,
|
|
50
|
+
alt: collection.name ?? "Coin",
|
|
51
|
+
fill: true,
|
|
52
|
+
sizes: "(min-width:1280px) 25vw, (min-width:1024px) 33vw, (min-width:640px) 50vw, 100vw",
|
|
53
|
+
className: "object-cover transition-transform duration-500 group-hover:scale-105",
|
|
54
|
+
unoptimized: true
|
|
55
|
+
}
|
|
56
|
+
) : /* @__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 }) }),
|
|
57
|
+
/* @__PURE__ */ jsx("span", { className: cn("absolute left-2 top-2 rounded-full px-2 py-0.5 text-[10px] font-semibold backdrop-blur-md", KIND_OVER_IMAGE[m.kind]), children: kindLabel(m.kind) })
|
|
58
|
+
] }),
|
|
59
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-3", children: [
|
|
60
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
61
|
+
/* @__PURE__ */ jsx("div", { className: "truncate font-semibold leading-tight", children: collection.name ?? "Untitled coin" }),
|
|
62
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
66
63
|
] }),
|
|
67
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
68
|
-
/* @__PURE__ */
|
|
69
|
-
|
|
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
|
-
] })
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: "Price" }),
|
|
66
|
+
/* @__PURE__ */ jsx(PriceValue, { price: m.price, isLoading: m.isLoading })
|
|
82
67
|
] })
|
|
83
|
-
] })
|
|
84
|
-
] });
|
|
68
|
+
] })
|
|
69
|
+
] }) });
|
|
85
70
|
}
|
|
86
71
|
function CoinRow({ collection, usePrice, href }) {
|
|
87
72
|
const m = useTileModel({ collection, usePrice });
|
|
@@ -91,20 +76,19 @@ function CoinRow({ collection, usePrice, href }) {
|
|
|
91
76
|
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold", children: collection.name ?? "Untitled coin" }),
|
|
92
77
|
/* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: collection.symbol ?? "\u2014" })
|
|
93
78
|
] }),
|
|
94
|
-
/* @__PURE__ */ jsx(
|
|
95
|
-
/* @__PURE__ */ jsx("span", { className: "w-
|
|
96
|
-
/* @__PURE__ */ jsx("span", { className: "hidden shrink-0 text-sm font-medium text-primary sm:inline-block", children: "Trade" })
|
|
79
|
+
/* @__PURE__ */ jsx("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]), children: kindLabel(m.kind) }),
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "w-28 shrink-0 text-right text-sm", children: /* @__PURE__ */ jsx(PriceValue, { price: m.price, isLoading: m.isLoading }) })
|
|
97
81
|
] });
|
|
98
82
|
}
|
|
99
83
|
function CoinCardSkeleton() {
|
|
100
84
|
return /* @__PURE__ */ jsxs("div", { className: "card-base flex flex-col", children: [
|
|
101
85
|
/* @__PURE__ */ jsx("div", { className: "aspect-square w-full animate-pulse bg-muted" }),
|
|
102
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-
|
|
86
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 p-3", children: [
|
|
103
87
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
104
88
|
/* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted-foreground/20 animate-pulse" }),
|
|
105
89
|
/* @__PURE__ */ jsx("div", { className: "h-3 w-12 rounded bg-muted-foreground/20 animate-pulse" })
|
|
106
90
|
] }),
|
|
107
|
-
/* @__PURE__ */ jsx("div", { className: "h-
|
|
91
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-full rounded bg-muted-foreground/10 animate-pulse" })
|
|
108
92
|
] })
|
|
109
93
|
] });
|
|
110
94
|
}
|
|
@@ -1 +1 @@
|
|
|
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":[]}
|
|
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 whole tile\n * links to the coin page (where trading happens), and the only stat is the live\n * spot price with its quote-currency icon. FDV / holders / a \"verified\" mark are\n * intentionally not 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 { CurrencyIcon } from \"./currency-icon.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\n// Kind tag on a card surface (table row) — brand hues, never red.\nconst KIND_TAG: Record<string, string> = {\n creator: \"border-brand-blue/30 bg-brand-blue/10 text-brand-blue\",\n memecoin: \"border-brand-purple/30 bg-brand-purple/10 text-brand-purple\",\n};\n// Kind tag layered over artwork — solid brand pill, white text for legibility.\nconst KIND_OVER_IMAGE: Record<string, string> = {\n creator: \"bg-brand-blue/85 text-white\",\n memecoin: \"bg-brand-purple/85 text-white\",\n};\n// Art-fallback gradient (no logo) — vivid brand wash.\nconst KIND_FALLBACK: Record<string, string> = {\n creator: \"from-brand-blue to-brand-purple\",\n memecoin: \"from-brand-purple to-brand-rose\",\n};\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\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 return { price, isLoading, kind, logo, initials };\n}\n\nfunction PriceValue({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"inline-block h-4 w-14 rounded bg-muted-foreground/20 animate-pulse\" />;\n if (!price) return <span className=\"text-muted-foreground\">—</span>;\n return (\n <span className=\"inline-flex items-center gap-1 font-semibold tabular-nums\">\n <CurrencyIcon symbol={price.quoteSymbol} size={14} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n );\n}\n\nexport function CoinCard({ collection, usePrice, href }: CoinTileProps) {\n const m = useTileModel({ collection, usePrice });\n return (\n <MotionCard className=\"card-base group 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 <span className={cn(\"absolute left-2 top-2 rounded-full px-2 py-0.5 text-[10px] font-semibold backdrop-blur-md\", KIND_OVER_IMAGE[m.kind])}>\n {kindLabel(m.kind)}\n </span>\n </div>\n\n {/* Body */}\n <div className=\"flex flex-col gap-2 p-3\">\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 <div className=\"flex items-baseline justify-between gap-2\">\n <span className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">Price</span>\n <PriceValue price={m.price} isLoading={m.isLoading} />\n </div>\n </div>\n </Link>\n </MotionCard>\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 <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 {kindLabel(m.kind)}\n </span>\n <span className=\"w-28 shrink-0 text-right text-sm\">\n <PriceValue price={m.price} isLoading={m.isLoading} />\n </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-2 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-4 w-full rounded bg-muted-foreground/10 animate-pulse\" />\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|