@medialane/ui 0.134.0 → 0.134.2

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.
@@ -43,18 +43,18 @@ var import_ipfs = require("../utils/ipfs.js");
43
43
  var import_currency_icon = require("./currency-icon.js");
44
44
  var import_coins = require("../data/coins.js");
45
45
  var import_format = require("../utils/format.js");
46
- const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4";
46
+ const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
47
47
  const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
48
48
  function CoinAvatar({ collection, size = 36 }) {
49
49
  const logoUri = collection.profile?.image ?? collection.image;
50
50
  const logo = logoUri ? (0, import_ipfs.ipfsToHttp)(logoUri) : null;
51
51
  const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
52
- const hue = (0, import_coins.coinAccentHue)(collection.symbol ?? collection.name ?? collection.contractAddress);
52
+ const accent = (0, import_coins.coinAccentToken)(collection.symbol ?? collection.name ?? collection.contractAddress);
53
53
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
54
54
  "div",
55
55
  {
56
- className: "relative shrink-0 overflow-hidden rounded-full",
57
- style: { width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` },
56
+ className: (0, import_cn.cn)("relative shrink-0 overflow-hidden rounded-full", accent),
57
+ style: { width: size, height: size },
58
58
  children: logo ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_image.default, { src: logo, alt: "", fill: true, sizes: `${size}px`, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
59
59
  "span",
60
60
  {
@@ -66,9 +66,11 @@ function CoinAvatar({ collection, size = 36 }) {
66
66
  }
67
67
  );
68
68
  }
69
- function PriceCell({ price, isLoading }) {
69
+ function PriceCell({ price, status, isLoading }) {
70
70
  if (isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" });
71
- if (!price) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
71
+ if (!price) {
72
+ return status === "pre-launch" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
73
+ }
72
74
  const usd = price.quoteUsdRate != null ? (0, import_format.formatUsdPrice)(price.quotePerCoin * price.quoteUsdRate) : null;
73
75
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "block text-right leading-tight", children: [
74
76
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block font-semibold tabular-nums", children: usd ?? (0, import_coins.formatCoinPrice)(price.quotePerCoin) }),
@@ -79,13 +81,16 @@ function PriceCell({ price, isLoading }) {
79
81
  ] });
80
82
  }
81
83
  function CoinRow({ collection, usePrice, href, showKind = false }) {
82
- const { price, isLoading } = usePrice(collection);
83
- const fdv = (0, import_coins.formatFdvUsd)(price, collection.totalSupply);
84
+ const { price, status, isLoading } = usePrice(collection);
84
85
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
85
86
  import_link.default,
86
87
  {
87
88
  href,
88
- className: (0, import_cn.cn)(COIN_GRID, "border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40"),
89
+ className: (0, import_cn.cn)(
90
+ COIN_GRID,
91
+ "border-b border-border/40 px-2 py-2.5 text-sm transition-colors",
92
+ "hover:bg-muted/40 active:bg-muted/60"
93
+ ),
89
94
  children: [
90
95
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex min-w-0 items-center gap-3", children: [
91
96
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CoinAvatar, { collection }),
@@ -100,8 +105,7 @@ function CoinRow({ collection, usePrice, href, showKind = false }) {
100
105
  ] })
101
106
  ] })
102
107
  ] }),
103
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceCell, { price, isLoading }),
104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hidden text-right tabular-nums text-muted-foreground sm:block", children: isLoading ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-12 animate-pulse rounded bg-muted-foreground/20" }) : fdv ?? "\u2014" })
108
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PriceCell, { price, status, isLoading })
105
109
  ]
106
110
  }
107
111
  );
@@ -115,8 +119,7 @@ function CoinRowSkeleton() {
115
119
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "block h-3 w-14 animate-pulse rounded bg-muted-foreground/10" })
116
120
  ] })
117
121
  ] }),
118
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" }),
119
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto hidden h-4 w-12 animate-pulse rounded bg-muted-foreground/20 sm:block" })
122
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" })
120
123
  ] });
121
124
  }
122
125
  // Annotate the CommonJS export names for ESM import in node:
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentHue, formatCoinPrice, formatFdvUsd,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\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 hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className=\"relative shrink-0 overflow-hidden rounded-full\"\n style={{ width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) return <span className=\"block text-right text-muted-foreground\">—</span>;\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, isLoading } = usePrice(collection);\n const fdv = formatFdvUsd(price, collection.totalSupply);\n\n return (\n <Link\n href={href}\n className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40\")}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} isLoading={isLoading} />\n\n <span className=\"hidden text-right tabular-nums text-muted-foreground sm:block\">\n {isLoading ? (\n <span className=\"ml-auto block h-4 w-12 animate-pulse rounded bg-muted-foreground/20\" />\n ) : (\n fdv ?? \"—\"\n )}\n </span>\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"ml-auto hidden h-4 w-12 animate-pulse rounded bg-muted-foreground/20 sm:block\" />\n </div>\n );\n}\n"],"mappings":"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","names":["Image","Link"]}
1
+ {"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentToken, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span className=\"block text-right text-muted-foreground\">—</span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} isLoading={isLoading} />\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n </div>\n );\n}\n"],"mappings":"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","names":["Image","Link"]}
@@ -1,11 +1,14 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CoinCollectionLike, CoinPriceLike } from '../data/coins.cjs';
3
+ import '@medialane/sdk';
3
4
 
5
+ type CoinMarketStatus = "live" | "pre-launch" | "unavailable";
4
6
  type UseCoinPrice = (collection: CoinCollectionLike) => {
5
7
  price: CoinPriceLike | null;
8
+ status?: CoinMarketStatus;
6
9
  isLoading: boolean;
7
10
  };
8
- declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4";
11
+ declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
9
12
  interface CoinRowProps {
10
13
  collection: CoinCollectionLike;
11
14
  usePrice: UseCoinPrice;
@@ -19,4 +22,4 @@ declare function CoinAvatar({ collection, size }: {
19
22
  declare function CoinRow({ collection, usePrice, href, showKind }: CoinRowProps): react_jsx_runtime.JSX.Element;
20
23
  declare function CoinRowSkeleton(): react_jsx_runtime.JSX.Element;
21
24
 
22
- export { COIN_GRID, CoinAvatar, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
25
+ export { COIN_GRID, CoinAvatar, type CoinMarketStatus, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
@@ -1,11 +1,14 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CoinCollectionLike, CoinPriceLike } from '../data/coins.js';
3
+ import '@medialane/sdk';
3
4
 
5
+ type CoinMarketStatus = "live" | "pre-launch" | "unavailable";
4
6
  type UseCoinPrice = (collection: CoinCollectionLike) => {
5
7
  price: CoinPriceLike | null;
8
+ status?: CoinMarketStatus;
6
9
  isLoading: boolean;
7
10
  };
8
- declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4";
11
+ declare const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
9
12
  interface CoinRowProps {
10
13
  collection: CoinCollectionLike;
11
14
  usePrice: UseCoinPrice;
@@ -19,4 +22,4 @@ declare function CoinAvatar({ collection, size }: {
19
22
  declare function CoinRow({ collection, usePrice, href, showKind }: CoinRowProps): react_jsx_runtime.JSX.Element;
20
23
  declare function CoinRowSkeleton(): react_jsx_runtime.JSX.Element;
21
24
 
22
- export { COIN_GRID, CoinAvatar, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
25
+ export { COIN_GRID, CoinAvatar, type CoinMarketStatus, CoinRow, type CoinRowProps, CoinRowSkeleton, type UseCoinPrice };
@@ -7,23 +7,22 @@ import { ipfsToHttp } from "../utils/ipfs.js";
7
7
  import { CurrencyIcon } from "./currency-icon.js";
8
8
  import {
9
9
  coinKind,
10
- coinAccentHue,
11
- formatCoinPrice,
12
- formatFdvUsd
10
+ coinAccentToken,
11
+ formatCoinPrice
13
12
  } from "../data/coins.js";
14
13
  import { formatUsdPrice } from "../utils/format.js";
15
- const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4";
14
+ const COIN_GRID = "grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4";
16
15
  const kindLabel = (kind) => kind === "creator" ? "Creator Coin" : "Memecoin";
17
16
  function CoinAvatar({ collection, size = 36 }) {
18
17
  const logoUri = collection.profile?.image ?? collection.image;
19
18
  const logo = logoUri ? ipfsToHttp(logoUri) : null;
20
19
  const initials = (collection.symbol ?? collection.name ?? "?").trim().slice(0, 2).toUpperCase();
21
- const hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);
20
+ const accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);
22
21
  return /* @__PURE__ */ jsx(
23
22
  "div",
24
23
  {
25
- className: "relative shrink-0 overflow-hidden rounded-full",
26
- style: { width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` },
24
+ className: cn("relative shrink-0 overflow-hidden rounded-full", accent),
25
+ style: { width: size, height: size },
27
26
  children: logo ? /* @__PURE__ */ jsx(Image, { src: logo, alt: "", fill: true, sizes: `${size}px`, className: "object-cover", unoptimized: true }) : /* @__PURE__ */ jsx(
28
27
  "span",
29
28
  {
@@ -35,9 +34,11 @@ function CoinAvatar({ collection, size = 36 }) {
35
34
  }
36
35
  );
37
36
  }
38
- function PriceCell({ price, isLoading }) {
37
+ function PriceCell({ price, status, isLoading }) {
39
38
  if (isLoading) return /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" });
40
- if (!price) return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
39
+ if (!price) {
40
+ return status === "pre-launch" ? /* @__PURE__ */ jsx("span", { className: "block text-right text-xs text-muted-foreground", children: "Not trading yet" }) : /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: "\u2014" });
41
+ }
41
42
  const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;
42
43
  return /* @__PURE__ */ jsxs("span", { className: "block text-right leading-tight", children: [
43
44
  /* @__PURE__ */ jsx("span", { className: "block font-semibold tabular-nums", children: usd ?? formatCoinPrice(price.quotePerCoin) }),
@@ -48,13 +49,16 @@ function PriceCell({ price, isLoading }) {
48
49
  ] });
49
50
  }
50
51
  function CoinRow({ collection, usePrice, href, showKind = false }) {
51
- const { price, isLoading } = usePrice(collection);
52
- const fdv = formatFdvUsd(price, collection.totalSupply);
52
+ const { price, status, isLoading } = usePrice(collection);
53
53
  return /* @__PURE__ */ jsxs(
54
54
  Link,
55
55
  {
56
56
  href,
57
- className: cn(COIN_GRID, "border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40"),
57
+ className: cn(
58
+ COIN_GRID,
59
+ "border-b border-border/40 px-2 py-2.5 text-sm transition-colors",
60
+ "hover:bg-muted/40 active:bg-muted/60"
61
+ ),
58
62
  children: [
59
63
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-3", children: [
60
64
  /* @__PURE__ */ jsx(CoinAvatar, { collection }),
@@ -69,8 +73,7 @@ function CoinRow({ collection, usePrice, href, showKind = false }) {
69
73
  ] })
70
74
  ] })
71
75
  ] }),
72
- /* @__PURE__ */ jsx(PriceCell, { price, isLoading }),
73
- /* @__PURE__ */ jsx("span", { className: "hidden text-right tabular-nums text-muted-foreground sm:block", children: isLoading ? /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-12 animate-pulse rounded bg-muted-foreground/20" }) : fdv ?? "\u2014" })
76
+ /* @__PURE__ */ jsx(PriceCell, { price, status, isLoading })
74
77
  ]
75
78
  }
76
79
  );
@@ -84,8 +87,7 @@ function CoinRowSkeleton() {
84
87
  /* @__PURE__ */ jsx("span", { className: "block h-3 w-14 animate-pulse rounded bg-muted-foreground/10" })
85
88
  ] })
86
89
  ] }),
87
- /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" }),
88
- /* @__PURE__ */ jsx("span", { className: "ml-auto hidden h-4 w-12 animate-pulse rounded bg-muted-foreground/20 sm:block" })
90
+ /* @__PURE__ */ jsx("span", { className: "ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20" })
89
91
  ] });
90
92
  }
91
93
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentHue, formatCoinPrice, formatFdvUsd,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[minmax(0,1fr)_8rem_7rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\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 hue = coinAccentHue(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className=\"relative shrink-0 overflow-hidden rounded-full\"\n style={{ width: size, height: size, backgroundColor: `hsl(${hue} 70% 52%)` }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, isLoading }: { price: CoinPriceLike | null; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) return <span className=\"block text-right text-muted-foreground\">—</span>;\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, isLoading } = usePrice(collection);\n const fdv = formatFdvUsd(price, collection.totalSupply);\n\n return (\n <Link\n href={href}\n className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors hover:bg-muted/40\")}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} isLoading={isLoading} />\n\n <span className=\"hidden text-right tabular-nums text-muted-foreground sm:block\">\n {isLoading ? (\n <span className=\"ml-auto block h-4 w-12 animate-pulse rounded bg-muted-foreground/20\" />\n ) : (\n fdv ?? \"—\"\n )}\n </span>\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"ml-auto hidden h-4 w-12 animate-pulse rounded bg-muted-foreground/20 sm:block\" />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/coin-row.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"./link.js\";\nimport Image from \"./image.js\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport {\n coinKind, coinAccentToken, formatCoinPrice,\n type CoinCollectionLike, type CoinPriceLike,\n} from \"../data/coins.js\";\nimport { formatUsdPrice } from \"../utils/format.js\";\n\nexport type CoinMarketStatus = \"live\" | \"pre-launch\" | \"unavailable\";\n\nexport type UseCoinPrice = (collection: CoinCollectionLike) => {\n price: CoinPriceLike | null;\n status?: CoinMarketStatus;\n isLoading: boolean;\n};\n\nexport const COIN_GRID = \"grid grid-cols-[minmax(0,1fr)_8rem] items-center gap-3 sm:gap-4\";\n\nconst kindLabel = (kind: string) => (kind === \"creator\" ? \"Creator Coin\" : \"Memecoin\");\n\nexport interface CoinRowProps {\n collection: CoinCollectionLike;\n usePrice: UseCoinPrice;\n href: string;\n showKind?: boolean;\n}\n\nexport function CoinAvatar({ collection, size = 36 }: { collection: CoinCollectionLike; size?: number }) {\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 accent = coinAccentToken(collection.symbol ?? collection.name ?? collection.contractAddress);\n\n return (\n <div\n className={cn(\"relative shrink-0 overflow-hidden rounded-full\", accent)}\n style={{ width: size, height: size }}\n >\n {logo ? (\n <Image src={logo} alt=\"\" fill sizes={`${size}px`} className=\"object-cover\" unoptimized />\n ) : (\n <span\n className=\"flex h-full w-full items-center justify-center font-bold leading-none text-white\"\n style={{ fontSize: size * 0.36 }}\n >\n {initials}\n </span>\n )}\n </div>\n );\n}\n\nfunction PriceCell({ price, status, isLoading }: { price: CoinPriceLike | null; status?: CoinMarketStatus; isLoading: boolean }) {\n if (isLoading) return <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />;\n if (!price) {\n return status === \"pre-launch\" ? (\n <span className=\"block text-right text-xs text-muted-foreground\">Not trading yet</span>\n ) : (\n <span className=\"block text-right text-muted-foreground\">—</span>\n );\n }\n\n const usd = price.quoteUsdRate != null ? formatUsdPrice(price.quotePerCoin * price.quoteUsdRate) : null;\n\n return (\n <span className=\"block text-right leading-tight\">\n <span className=\"block font-semibold tabular-nums\">{usd ?? formatCoinPrice(price.quotePerCoin)}</span>\n <span className=\"mt-0.5 inline-flex items-center justify-end gap-1 text-2xs tabular-nums text-muted-foreground\">\n <CurrencyIcon symbol={price.quoteSymbol} size={10} />\n {formatCoinPrice(price.quotePerCoin)}\n </span>\n </span>\n );\n}\n\nexport function CoinRow({ collection, usePrice, href, showKind = false }: CoinRowProps) {\n const { price, status, isLoading } = usePrice(collection);\n\n return (\n <Link\n href={href}\n className={cn(\n COIN_GRID,\n \"border-b border-border/40 px-2 py-2.5 text-sm transition-colors\",\n \"hover:bg-muted/40 active:bg-muted/60\"\n )}\n >\n <span className=\"flex min-w-0 items-center gap-3\">\n <CoinAvatar collection={collection} />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-semibold leading-tight\">{collection.name ?? \"Untitled coin\"}</span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {collection.symbol ?? \"—\"}\n {showKind && <span className=\"text-muted-foreground/60\"> · {kindLabel(coinKind(collection.service))}</span>}\n </span>\n </span>\n </span>\n\n <PriceCell price={price} status={status} isLoading={isLoading} />\n </Link>\n );\n}\n\nexport function CoinRowSkeleton() {\n return (\n <div className={cn(COIN_GRID, \"border-b border-border/40 px-2 py-2.5\")}>\n <span className=\"flex items-center gap-3\">\n <span className=\"h-9 w-9 shrink-0 animate-pulse rounded-full bg-muted-foreground/20\" />\n <span className=\"space-y-1.5\">\n <span className=\"block h-4 w-28 animate-pulse rounded bg-muted-foreground/20\" />\n <span className=\"block h-3 w-14 animate-pulse rounded bg-muted-foreground/10\" />\n </span>\n </span>\n <span className=\"ml-auto block h-4 w-16 animate-pulse rounded bg-muted-foreground/20\" />\n </div>\n );\n}\n"],"mappings":";AA4CQ,cA4BF,YA5BE;AA1CR,OAAO,UAAU;AACjB,OAAO,WAAW;AAClB,SAAS,UAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EAAU;AAAA,EAAiB;AAAA,OAEtB;AACP,SAAS,sBAAsB;AAUxB,MAAM,YAAY;AAEzB,MAAM,YAAY,CAAC,SAAkB,SAAS,YAAY,iBAAiB;AASpE,SAAS,WAAW,EAAE,YAAY,OAAO,GAAG,GAAsD;AACvG,QAAM,UAAU,WAAW,SAAS,SAAS,WAAW;AACxD,QAAM,OAAO,UAAU,WAAW,OAAO,IAAI;AAC7C,QAAM,YAAY,WAAW,UAAU,WAAW,QAAQ,KAAK,KAAK,EAAE,MAAM,GAAG,CAAC,EAAE,YAAY;AAC9F,QAAM,SAAS,gBAAgB,WAAW,UAAU,WAAW,QAAQ,WAAW,eAAe;AAEjG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,kDAAkD,MAAM;AAAA,MACtE,OAAO,EAAE,OAAO,MAAM,QAAQ,KAAK;AAAA,MAElC,iBACC,oBAAC,SAAM,KAAK,MAAM,KAAI,IAAG,MAAI,MAAC,OAAO,GAAG,IAAI,MAAM,WAAU,gBAAe,aAAW,MAAC,IAEvF;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,UAAU,OAAO,KAAK;AAAA,UAE9B;AAAA;AAAA,MACH;AAAA;AAAA,EAEJ;AAEJ;AAEA,SAAS,UAAU,EAAE,OAAO,QAAQ,UAAU,GAAmF;AAC/H,MAAI,UAAW,QAAO,oBAAC,UAAK,WAAU,uEAAsE;AAC5G,MAAI,CAAC,OAAO;AACV,WAAO,WAAW,eAChB,oBAAC,UAAK,WAAU,kDAAiD,6BAAe,IAEhF,oBAAC,UAAK,WAAU,0CAAyC,oBAAC;AAAA,EAE9D;AAEA,QAAM,MAAM,MAAM,gBAAgB,OAAO,eAAe,MAAM,eAAe,MAAM,YAAY,IAAI;AAEnG,SACE,qBAAC,UAAK,WAAU,kCACd;AAAA,wBAAC,UAAK,WAAU,oCAAoC,iBAAO,gBAAgB,MAAM,YAAY,GAAE;AAAA,IAC/F,qBAAC,UAAK,WAAU,iGACd;AAAA,0BAAC,gBAAa,QAAQ,MAAM,aAAa,MAAM,IAAI;AAAA,MAClD,gBAAgB,MAAM,YAAY;AAAA,OACrC;AAAA,KACF;AAEJ;AAEO,SAAS,QAAQ,EAAE,YAAY,UAAU,MAAM,WAAW,MAAM,GAAiB;AACtF,QAAM,EAAE,OAAO,QAAQ,UAAU,IAAI,SAAS,UAAU;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,UAAK,WAAU,mCACd;AAAA,8BAAC,cAAW,YAAwB;AAAA,UACpC,qBAAC,UAAK,WAAU,WACd;AAAA,gCAAC,UAAK,WAAU,8CAA8C,qBAAW,QAAQ,iBAAgB;AAAA,YACjG,qBAAC,UAAK,WAAU,gDACb;AAAA,yBAAW,UAAU;AAAA,cACrB,YAAY,qBAAC,UAAK,WAAU,4BAA2B;AAAA;AAAA,gBAAI,UAAU,SAAS,WAAW,OAAO,CAAC;AAAA,iBAAE;AAAA,eACtG;AAAA,aACF;AAAA,WACF;AAAA,QAEA,oBAAC,aAAU,OAAc,QAAgB,WAAsB;AAAA;AAAA;AAAA,EACjE;AAEJ;AAEO,SAAS,kBAAkB;AAChC,SACE,qBAAC,SAAI,WAAW,GAAG,WAAW,uCAAuC,GACnE;AAAA,yBAAC,UAAK,WAAU,2BACd;AAAA,0BAAC,UAAK,WAAU,sEAAqE;AAAA,MACrF,qBAAC,UAAK,WAAU,eACd;AAAA,4BAAC,UAAK,WAAU,+DAA8D;AAAA,QAC9E,oBAAC,UAAK,WAAU,+DAA8D;AAAA,SAChF;AAAA,OACF;AAAA,IACA,oBAAC,UAAK,WAAU,uEAAsE;AAAA,KACxF;AAEJ;","names":[]}
@@ -101,8 +101,7 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
101
101
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
102
102
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: (0, import_cn.cn)(import_coin_row.COIN_GRID, "border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground"), children: [
103
103
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Token" }),
104
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-right", children: "Price" }),
105
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hidden text-right sm:block", children: "FDV" })
104
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-right", children: "Price" })
106
105
  ] }),
107
106
  isLoading && items.length === 0 ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_coin_row.CoinRowSkeleton, {}, i)) : items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "py-16 text-center text-sm text-muted-foreground", children: query.trim() ? `No coins match "${query.trim()}".` : "No coins yet." }) : items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
108
107
  import_coin_row.CoinRow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { coinKind, type CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins &amp; memecoins</h1>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>}\n {sort !== \"recent\" && <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>}\n </div>\n )}\n\n <div>\n <div className={cn(COIN_GRID, \"border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground\")}>\n <span>Token</span>\n <span className=\"text-right\">Price</span>\n <span className=\"hidden text-right sm:block\">FDV</span>\n </div>\n\n {isLoading && items.length === 0 ? (\n Array.from({ length: 6 }).map((_, i) => <CoinRowSkeleton key={i} />)\n ) : items.length === 0 ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </p>\n ) : (\n items.map((c) => (\n <CoinRow\n key={`${c.chain}-${c.contractAddress}`}\n collection={c}\n usePrice={usePrice}\n href={coinHref(c)}\n showKind={showKind}\n />\n ))\n )}\n </div>\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAmEU;AAjEV,mBAA6C;AAC7C,0BAAoD;AACpD,gBAAmB;AACnB,sBAAuE;AACvE,mBAAkD;AAiBlD,MAAM,cAAsD;AAAA,EAC1D,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,EAC7B,EAAE,OAAO,iBAAiB,OAAO,UAAU;AAAA,EAC3C,EAAE,OAAO,aAAa,OAAO,WAAW;AAC1C;AAEA,MAAM,eAAqD;AAAA,EACzD,EAAE,OAAO,qBAAqB,OAAO,SAAS;AAAA,EAC9C,EAAE,OAAO,QAAQ,OAAO,OAAO;AACjC;AAEA,MAAM,cAAc,CAAC,MAAkB,YAAY,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS;AACxF,MAAM,YAAY,CAAC,MAAgB,aAAa,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS;AAE9E,SAAS,cAAc,EAAE,UAAU,UAAU,UAAU,UAAU,KAAK,GAAuB;AAClG,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAqB,KAAK;AACtD,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAmB,QAAQ;AACnD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,EAAE;AACrC,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAS,KAAK;AAEpD,QAAM,eAAe,WAAW,QAAQ,IAAI,MAAM,SAAS,WAAW,IAAI;AAE1E,8BAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAClB,UAAM,QAAQ,CAAC,MAAqB,EAAE,QAAQ,YAAY,eAAe,KAAK;AAC9E,WAAO,iBAAiB,WAAW,KAAK;AACxC,WAAO,MAAM,OAAO,oBAAoB,WAAW,KAAK;AAAA,EAC1D,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,EAAE,aAAa,UAAU,IAAI,SAAS,EAAE,QAAQ,KAAK,CAAC;AAC5D,QAAM,YAAQ,sBAAQ,MAAM;AAC1B,UAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,QAAI,CAAC,EAAG,QAAO;AACf,WAAO,YAAY;AAAA,MACjB,CAAC,OAAO,EAAE,QAAQ,IAAI,YAAY,EAAE,SAAS,CAAC,MAAM,EAAE,UAAU,IAAI,YAAY,EAAE,SAAS,CAAC;AAAA,IAC9F;AAAA,EACF,GAAG,CAAC,aAAa,KAAK,CAAC;AAEvB,QAAM,eAAW,sBAAQ,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,UAAM,uBAAS,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,KAAK,CAAC;AAE/F,SACE,6CAAC,SAAI,WAAU,aACZ;AAAA,eACC,6CAAC,SAAI,WAAU,aACb;AAAA,mDAAC,SAAI,WAAU,wCACb;AAAA,oDAAC,6BAAM,WAAU,WAAU;AAAA,QAC3B,4CAAC,UAAK,WAAU,yBAAwB,mBAAK;AAAA,SAC/C;AAAA,MACA,4CAAC,QAAG,WAAU,YAAW,uCAA6B;AAAA,OACxD;AAAA,IAGF,6CAAC,SAAI,WAAU,2BACb;AAAA,mDAAC,SAAI,WAAU,mBACb;AAAA,oDAAC,8BAAO,WAAU,kGAAiG;AAAA,QACnH;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,aAAY;AAAA,YACZ,WAAU;AAAA;AAAA,QACZ;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,SAAS,MAAM,eAAe,IAAI;AAAA,UAClC,WAAU;AAAA,UAEV;AAAA,wDAAC,yCAAkB,WAAU,eAAc;AAAA,YAAE;AAAA,YAE5C,cAAc,KACb,4CAAC,UAAK,WAAU,0IACb,uBACH;AAAA;AAAA;AAAA,MAEJ;AAAA,OACF;AAAA,IAEC,cAAc,KACb,6CAAC,SAAI,WAAU,uCACZ;AAAA,iBAAW,SAAS,4CAAC,QAAK,SAAS,MAAM,UAAU,KAAK,GAAI,sBAAY,MAAM,GAAE;AAAA,MAChF,SAAS,YAAY,4CAAC,QAAK,SAAS,MAAM,QAAQ,QAAQ,GAAI,oBAAU,IAAI,GAAE;AAAA,OACjF;AAAA,IAGF,6CAAC,SACC;AAAA,mDAAC,SAAI,eAAW,cAAG,2BAAW,qGAAqG,GACjI;AAAA,oDAAC,UAAK,mBAAK;AAAA,QACX,4CAAC,UAAK,WAAU,cAAa,mBAAK;AAAA,QAClC,4CAAC,UAAK,WAAU,8BAA6B,iBAAG;AAAA,SAClD;AAAA,MAEC,aAAa,MAAM,WAAW,IAC7B,MAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MAAM,4CAAC,qCAAqB,CAAG,CAAE,IACjE,MAAM,WAAW,IACnB,4CAAC,OAAE,WAAU,mDACV,gBAAM,KAAK,IAAI,mBAAmB,MAAM,KAAK,CAAC,OAAO,iBACxD,IAEA,MAAM,IAAI,CAAC,MACT;AAAA,QAAC;AAAA;AAAA,UAEC,YAAY;AAAA,UACZ;AAAA,UACA,MAAM,SAAS,CAAC;AAAA,UAChB;AAAA;AAAA,QAJK,GAAG,EAAE,KAAK,IAAI,EAAE,eAAe;AAAA,MAKtC,CACD;AAAA,OAEL;AAAA,IAEC,eACC,6CAAC,SAAI,WAAU,2DAA0D,MAAK,UAAS,cAAW,QAAO,cAAW,WAClH;AAAA,kDAAC,SAAI,WAAU,sDAAqD,SAAS,MAAM,eAAe,KAAK,GAAG;AAAA,MAC1G,6CAAC,SAAI,WAAU,0GACb;AAAA,qDAAC,SAAI,WAAU,qCACb;AAAA,uDAAC,QAAG,WAAU,+CACZ;AAAA,wDAAC,yCAAkB,WAAU,wBAAuB;AAAA,YAAE;AAAA,aAExD;AAAA,UACC,cAAc,KACb;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,MAAM;AAAE,0BAAU,KAAK;AAAG,wBAAQ,QAAQ;AAAA,cAAG;AAAA,cACtD,WAAU;AAAA,cACX;AAAA;AAAA,UAED;AAAA,WAEJ;AAAA,QAEA,4CAAC,eAAY,OAAM,QAChB,sBAAY,IAAI,CAAC,EAAE,OAAO,MAAM,MAC/B,4CAAC,cAAuB,QAAQ,WAAW,OAAO,SAAS,MAAM,UAAU,KAAK,GAAI,mBAAnE,KAAyE,CAC3F,GACH;AAAA,QAEA,4CAAC,eAAY,OAAM,QAChB,uBAAa,IAAI,CAAC,EAAE,OAAO,MAAM,MAChC,4CAAC,cAAuB,QAAQ,SAAS,OAAO,SAAS,MAAM,QAAQ,KAAK,GAAI,mBAA/D,KAAqE,CACvF,GACH;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS,MAAM,eAAe,KAAK;AAAA,YACnC,WAAU;AAAA,YACX;AAAA;AAAA,cACO,MAAM;AAAA,cAAO;AAAA,cAAE,MAAM,WAAW,IAAI,SAAS;AAAA;AAAA;AAAA,QACrD;AAAA,SACF;AAAA,OACF;AAAA,KAEJ;AAEJ;AAEA,SAAS,YAAY,EAAE,OAAO,SAAS,GAAiD;AACtF,SACE,6CAAC,SAAI,WAAU,aACb;AAAA,gDAAC,OAAE,WAAU,8CAA8C,iBAAM;AAAA,IACjE,4CAAC,SAAI,WAAU,0BAA0B,UAAS;AAAA,KACpD;AAEJ;AAEA,SAAS,WAAW,EAAE,QAAQ,SAAS,SAAS,GAAwE;AACtH,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,eAAW;AAAA,QACT;AAAA,QACA,SAAS,8CAA8C;AAAA,MACzD;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAEA,SAAS,KAAK,EAAE,UAAU,QAAQ,GAAuD;AACvF,SACE,6CAAC,UAAK,WAAU,mIACb;AAAA;AAAA,IACD,4CAAC,YAAO,SAAS,SAAS,cAAW,gBAAe,WAAU,yBAC5D,sDAAC,yBAAE,WAAU,WAAU,GACzB;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { coinKind, type CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins &amp; memecoins</h1>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>}\n {sort !== \"recent\" && <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>}\n </div>\n )}\n\n <div>\n <div className={cn(COIN_GRID, \"border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground\")}>\n <span>Token</span>\n <span className=\"text-right\">Price</span>\n </div>\n\n {isLoading && items.length === 0 ? (\n Array.from({ length: 6 }).map((_, i) => <CoinRowSkeleton key={i} />)\n ) : items.length === 0 ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </p>\n ) : (\n items.map((c) => (\n <CoinRow\n key={`${c.chain}-${c.contractAddress}`}\n collection={c}\n usePrice={usePrice}\n href={coinHref(c)}\n showKind={showKind}\n />\n ))\n )}\n </div>\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { UseCoinPrice } from './coin-row.cjs';
3
3
  import { CoinCollectionLike } from '../data/coins.cjs';
4
+ import '@medialane/sdk';
4
5
 
5
6
  type CoinFilter = "all" | "creator" | "memecoin";
6
7
  type CoinSort = "recent" | "name";
@@ -1,6 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { UseCoinPrice } from './coin-row.js';
3
3
  import { CoinCollectionLike } from '../data/coins.js';
4
+ import '@medialane/sdk';
4
5
 
5
6
  type CoinFilter = "all" | "creator" | "memecoin";
6
7
  type CoinSort = "recent" | "name";
@@ -78,8 +78,7 @@ function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }) {
78
78
  /* @__PURE__ */ jsxs("div", { children: [
79
79
  /* @__PURE__ */ jsxs("div", { className: cn(COIN_GRID, "border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground"), children: [
80
80
  /* @__PURE__ */ jsx("span", { children: "Token" }),
81
- /* @__PURE__ */ jsx("span", { className: "text-right", children: "Price" }),
82
- /* @__PURE__ */ jsx("span", { className: "hidden text-right sm:block", children: "FDV" })
81
+ /* @__PURE__ */ jsx("span", { className: "text-right", children: "Price" })
83
82
  ] }),
84
83
  isLoading && items.length === 0 ? Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx(CoinRowSkeleton, {}, i)) : items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-16 text-center text-sm text-muted-foreground", children: query.trim() ? `No coins match "${query.trim()}".` : "No coins yet." }) : items.map((c) => /* @__PURE__ */ jsx(
85
84
  CoinRow,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { coinKind, type CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins &amp; memecoins</h1>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>}\n {sort !== \"recent\" && <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>}\n </div>\n )}\n\n <div>\n <div className={cn(COIN_GRID, \"border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground\")}>\n <span>Token</span>\n <span className=\"text-right\">Price</span>\n <span className=\"hidden text-right sm:block\">FDV</span>\n </div>\n\n {isLoading && items.length === 0 ? (\n Array.from({ length: 6 }).map((_, i) => <CoinRowSkeleton key={i} />)\n ) : items.length === 0 ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </p>\n ) : (\n items.map((c) => (\n <CoinRow\n key={`${c.chain}-${c.contractAddress}`}\n collection={c}\n usePrice={usePrice}\n href={coinHref(c)}\n showKind={showKind}\n />\n ))\n )}\n </div>\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinRow, CoinRowSkeleton, COIN_GRID, type UseCoinPrice } from \"./coin-row.js\";\nimport { coinKind, type CoinCollectionLike } from \"../data/coins.js\";\n\nexport type CoinFilter = \"all\" | \"creator\" | \"memecoin\";\nexport type CoinSort = \"recent\" | \"name\";\nexport type UseCoins = (opts: { filter: CoinFilter; sort: CoinSort }) => {\n collections: CoinCollectionLike[];\n isLoading: boolean;\n};\n\nexport interface CoinsExplorerProps {\n useCoins: UseCoins;\n usePrice: UseCoinPrice;\n\n coinHref: (collection: CoinCollectionLike) => string;\n heading?: boolean;\n}\n\nconst FILTER_TABS: { label: string; value: CoinFilter }[] = [\n { label: \"All\", value: \"all\" },\n { label: \"Creator Coins\", value: \"creator\" },\n { label: \"Memecoins\", value: \"memecoin\" },\n];\n\nconst SORT_OPTIONS: { label: string; value: CoinSort }[] = [\n { label: \"Recently launched\", value: \"recent\" },\n { label: \"Name\", value: \"name\" },\n];\n\nconst filterLabel = (v: CoinFilter) => FILTER_TABS.find((t) => t.value === v)?.label ?? \"\";\nconst sortLabel = (v: CoinSort) => SORT_OPTIONS.find((o) => o.value === v)?.label ?? \"\";\n\nexport function CoinsExplorer({ useCoins, usePrice, coinHref, heading = true }: CoinsExplorerProps) {\n const [filter, setFilter] = useState<CoinFilter>(\"all\");\n const [sort, setSort] = useState<CoinSort>(\"recent\");\n const [query, setQuery] = useState(\"\");\n const [filtersOpen, setFiltersOpen] = useState(false);\n\n const filterCount = (filter !== \"all\" ? 1 : 0) + (sort !== \"recent\" ? 1 : 0);\n\n useEffect(() => {\n if (!filtersOpen) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && setFiltersOpen(false);\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [filtersOpen]);\n\n const { collections, isLoading } = useCoins({ filter, sort });\n const items = useMemo(() => {\n const q = query.trim().toLowerCase();\n if (!q) return collections;\n return collections.filter(\n (c) => (c.name ?? \"\").toLowerCase().includes(q) || (c.symbol ?? \"\").toLowerCase().includes(q)\n );\n }, [collections, query]);\n\n const showKind = useMemo(() => new Set(items.map((c) => coinKind(c.service))).size > 1, [items]);\n\n return (\n <div className=\"space-y-5\">\n {heading && (\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-2 text-primary\">\n <Coins className=\"h-5 w-5\" />\n <span className=\"text-sm font-semibold\">Coins</span>\n </div>\n <h1 className=\"text-3xl\">Creator coins &amp; memecoins</h1>\n </div>\n )}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"pointer-events-none absolute left-3 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search coins by name or symbol…\"\n className=\"w-full rounded-lg border border-border bg-background py-2 pl-9 pr-3 text-sm outline-none focus:border-primary/50\"\n />\n </div>\n <button\n onClick={() => setFiltersOpen(true)}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border px-3 py-2 text-xs font-medium text-foreground hover:border-primary/50\"\n >\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {filterCount > 0 && (\n <span className=\"ml-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 text-2xs font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n </div>\n\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>}\n {sort !== \"recent\" && <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>}\n </div>\n )}\n\n <div>\n <div className={cn(COIN_GRID, \"border-b border-border px-2 pb-2 text-2xs font-medium uppercase tracking-wide text-muted-foreground\")}>\n <span>Token</span>\n <span className=\"text-right\">Price</span>\n </div>\n\n {isLoading && items.length === 0 ? (\n Array.from({ length: 6 }).map((_, i) => <CoinRowSkeleton key={i} />)\n ) : items.length === 0 ? (\n <p className=\"py-16 text-center text-sm text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </p>\n ) : (\n items.map((c) => (\n <CoinRow\n key={`${c.chain}-${c.contractAddress}`}\n collection={c}\n usePrice={usePrice}\n href={coinHref(c)}\n showKind={showKind}\n />\n ))\n )}\n </div>\n\n {filtersOpen && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Filters\">\n <div className=\"absolute inset-0 bg-background/70 backdrop-blur-sm\" onClick={() => setFiltersOpen(false)} />\n <div className=\"relative z-10 w-full max-w-sm space-y-5 overflow-hidden rounded-[calc(var(--radius)*1.25)] bg-card p-5\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"flex items-center gap-2 text-base font-bold\">\n <SlidersHorizontal className=\"h-4 w-4 text-primary\" />\n Filters\n </h2>\n {filterCount > 0 && (\n <button\n onClick={() => { setFilter(\"all\"); setSort(\"recent\"); }}\n className=\"text-xs font-medium text-muted-foreground hover:text-foreground\"\n >\n Clear all\n </button>\n )}\n </div>\n\n <FilterGroup label=\"Type\">\n {FILTER_TABS.map(({ label, value }) => (\n <PillButton key={value} active={filter === value} onClick={() => setFilter(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <FilterGroup label=\"Sort\">\n {SORT_OPTIONS.map(({ label, value }) => (\n <PillButton key={value} active={sort === value} onClick={() => setSort(value)}>{label}</PillButton>\n ))}\n </FilterGroup>\n\n <button\n onClick={() => setFiltersOpen(false)}\n className=\"w-full rounded-lg bg-gradient-to-r from-brand-blue to-brand-purple py-2.5 text-sm font-semibold text-white\"\n >\n Show {items.length} {items.length === 1 ? \"coin\" : \"coins\"}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction FilterGroup({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-2\">\n <p className=\"text-2xs font-medium text-muted-foreground\">{label}</p>\n <div className=\"flex flex-wrap gap-1.5\">{children}</div>\n </div>\n );\n}\n\nfunction PillButton({ active, onClick, children }: { active: boolean; onClick: () => void; children: React.ReactNode }) {\n return (\n <button\n onClick={onClick}\n className={cn(\n \"rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors\",\n active ? \"border-primary bg-primary/10 text-primary\" : \"border-border text-muted-foreground hover:border-primary/50 hover:text-foreground\"\n )}\n >\n {children}\n </button>\n );\n}\n\nfunction Chip({ children, onClear }: { children: React.ReactNode; onClear: () => void }) {\n return (\n <span className=\"inline-flex items-center gap-1 rounded-full border border-primary/30 bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {children}\n <button onClick={onClear} aria-label=\"Clear filter\" className=\"hover:text-primary/60\">\n <X className=\"h-3 w-3\" />\n </button>\n </span>\n );\n}\n"],"mappings":"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","names":[]}
@@ -18,27 +18,43 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var coins_exports = {};
20
20
  __export(coins_exports, {
21
- coinAccentHue: () => coinAccentHue,
21
+ coinAccentToken: () => coinAccentToken,
22
22
  coinKind: () => coinKind,
23
+ coinServiceIds: () => coinServiceIds,
24
+ coinSupply: () => coinSupply,
23
25
  fdvUsd: () => fdvUsd,
24
26
  formatCoinPrice: () => formatCoinPrice,
25
- formatFdv: () => formatFdv,
26
- formatFdvUsd: () => formatFdvUsd
27
+ formatFdvUsd: () => formatFdvUsd,
28
+ isCoinService: () => isCoinService
27
29
  });
28
30
  module.exports = __toCommonJS(coins_exports);
31
+ var import_sdk = require("@medialane/sdk");
29
32
  var import_format = require("../utils/format.js");
30
33
  function coinKind(service) {
31
- return service === "external-erc20" ? "memecoin" : "creator";
34
+ return (0, import_sdk.getService)(service)?.provenance === "EXTERNAL" ? "memecoin" : "creator";
35
+ }
36
+ function isCoinService(def) {
37
+ return def.uiVariant === "coin";
38
+ }
39
+ function coinServiceIds(kind) {
40
+ const provenance = kind === "creator" ? "MEDIALANE" : "EXTERNAL";
41
+ return (0, import_sdk.listServices)().filter((s) => isCoinService(s) && s.provenance === provenance).map((s) => s.id);
32
42
  }
33
43
  function formatCoinPrice(n) {
34
44
  return (0, import_format.formatSmallDecimal)(n);
35
45
  }
36
- const ACCENT_HUES = [220, 258, 341, 23, 325];
37
- function coinAccentHue(seed) {
46
+ const ACCENT_TOKENS = [
47
+ "bg-brand-rose",
48
+ "bg-brand-maeve",
49
+ "bg-brand-purple",
50
+ "bg-brand-orange",
51
+ "bg-brand-blue"
52
+ ];
53
+ function coinAccentToken(seed) {
38
54
  const s = (seed ?? "?").trim().toUpperCase();
39
55
  let h = 0;
40
56
  for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) >>> 0;
41
- return ACCENT_HUES[h % ACCENT_HUES.length];
57
+ return ACCENT_TOKENS[h % ACCENT_TOKENS.length];
42
58
  }
43
59
  function abbreviate(n) {
44
60
  if (n >= 1e9) return `${(n / 1e9).toLocaleString(void 0, { maximumFractionDigits: 1 })}B`;
@@ -46,27 +62,38 @@ function abbreviate(n) {
46
62
  if (n >= 1e3) return `${(n / 1e3).toLocaleString(void 0, { maximumFractionDigits: 1 })}K`;
47
63
  return n.toLocaleString(void 0, { maximumFractionDigits: 2 });
48
64
  }
49
- function formatFdv(quotePerCoin, totalSupply, quoteSymbol) {
50
- if (quotePerCoin == null || !totalSupply) return null;
51
- const sym = quoteSymbol ?? "";
52
- const abbr = abbreviate(quotePerCoin * totalSupply);
53
- return sym ? `${abbr} ${sym}` : abbr;
65
+ function coinSupply(collection) {
66
+ const raw = collection.totalSupply;
67
+ if (raw == null || raw === "") return null;
68
+ let units;
69
+ try {
70
+ units = BigInt(raw);
71
+ } catch {
72
+ return null;
73
+ }
74
+ if (units <= 0n) return null;
75
+ const supply = Number(units) / 10 ** (collection.decimals ?? 18);
76
+ return isFinite(supply) && supply >= 1 ? supply : null;
54
77
  }
55
- function fdvUsd(price, totalSupply) {
56
- if (!price || !totalSupply || price.quoteUsdRate == null) return null;
57
- return price.quotePerCoin * totalSupply * price.quoteUsdRate;
78
+ function fdvUsd(price, collection) {
79
+ const supply = coinSupply(collection);
80
+ if (!price || supply == null || price.quoteUsdRate == null) return null;
81
+ const v = price.quotePerCoin * supply * price.quoteUsdRate;
82
+ return v > 0 && isFinite(v) ? v : null;
58
83
  }
59
- function formatFdvUsd(price, totalSupply) {
60
- const v = fdvUsd(price, totalSupply);
84
+ function formatFdvUsd(price, collection) {
85
+ const v = fdvUsd(price, collection);
61
86
  return v == null ? null : `$${abbreviate(v)}`;
62
87
  }
63
88
  // Annotate the CommonJS export names for ESM import in node:
64
89
  0 && (module.exports = {
65
- coinAccentHue,
90
+ coinAccentToken,
66
91
  coinKind,
92
+ coinServiceIds,
93
+ coinSupply,
67
94
  fdvUsd,
68
95
  formatCoinPrice,
69
- formatFdv,
70
- formatFdvUsd
96
+ formatFdvUsd,
97
+ isCoinService
71
98
  });
72
99
  //# sourceMappingURL=coins.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data/coins.ts"],"sourcesContent":["\n\nimport { formatSmallDecimal } from \"../utils/format.js\";\n\nexport type CoinKind = \"creator\" | \"memecoin\";\n\nexport interface CoinCollectionLike {\n contractAddress: string;\n chain?: string | null;\n name?: string | null;\n symbol?: string | null;\n image?: string | null;\n service?: string | null;\n claimedBy?: string | null;\n holderCount?: number | null;\n totalSupply?: number | null;\n profile?: { image?: string | null } | null;\n}\n\nexport interface CoinPriceLike {\n quotePerCoin: number;\n quoteSymbol: string | null;\n /** USD value of one unit of quoteSymbol, when known — lets price\n * displays show a fiat-equivalent alongside the on-chain quote. */\n quoteUsdRate?: number | null;\n}\n\nexport function coinKind(service: string | null | undefined): CoinKind {\n return service === \"external-erc20\" ? \"memecoin\" : \"creator\";\n}\n\nexport function formatCoinPrice(n: number): string {\n return formatSmallDecimal(n);\n}\n\nconst ACCENT_HUES = [220, 258, 341, 23, 325];\n\nexport function coinAccentHue(seed: string | null | undefined): number {\n const s = (seed ?? \"?\").trim().toUpperCase();\n let h = 0;\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;\n return ACCENT_HUES[h % ACCENT_HUES.length];\n}\n\nfunction abbreviate(n: number): string {\n if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}B`;\n if (n >= 1_000_000) return `${(n / 1_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}M`;\n if (n >= 1_000) return `${(n / 1_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}K`;\n return n.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\nexport function formatFdv(\n quotePerCoin: number | null | undefined,\n totalSupply: number | null | undefined,\n quoteSymbol: string | null | undefined\n): string | null {\n if (quotePerCoin == null || !totalSupply) return null;\n const sym = quoteSymbol ?? \"\";\n const abbr = abbreviate(quotePerCoin * totalSupply);\n return sym ? `${abbr} ${sym}` : abbr;\n}\n\nexport function fdvUsd(price: CoinPriceLike | null, totalSupply: number | null | undefined): number | null {\n if (!price || !totalSupply || price.quoteUsdRate == null) return null;\n return price.quotePerCoin * totalSupply * price.quoteUsdRate;\n}\n\nexport function formatFdvUsd(price: CoinPriceLike | null, totalSupply: number | null | undefined): string | null {\n const v = fdvUsd(price, totalSupply);\n return v == null ? null : `$${abbreviate(v)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,oBAAmC;AAyB5B,SAAS,SAAS,SAA8C;AACrE,SAAO,YAAY,mBAAmB,aAAa;AACrD;AAEO,SAAS,gBAAgB,GAAmB;AACjD,aAAO,kCAAmB,CAAC;AAC7B;AAEA,MAAM,cAAc,CAAC,KAAK,KAAK,KAAK,IAAI,GAAG;AAEpC,SAAS,cAAc,MAAyC;AACrE,QAAM,KAAK,QAAQ,KAAK,KAAK,EAAE,YAAY;AAC3C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,KAAK,IAAI,KAAK,EAAE,WAAW,CAAC,MAAO;AACtE,SAAO,YAAY,IAAI,YAAY,MAAM;AAC3C;AAEA,SAAS,WAAW,GAAmB;AACrC,MAAI,KAAK,IAAe,QAAO,IAAI,IAAI,KAAe,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC7G,MAAI,KAAK,IAAW,QAAO,IAAI,IAAI,KAAW,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AACrG,MAAI,KAAK,IAAO,QAAO,IAAI,IAAI,KAAO,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC7F,SAAO,EAAE,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACjE;AAEO,SAAS,UACd,cACA,aACA,aACe;AACf,MAAI,gBAAgB,QAAQ,CAAC,YAAa,QAAO;AACjD,QAAM,MAAM,eAAe;AAC3B,QAAM,OAAO,WAAW,eAAe,WAAW;AAClD,SAAO,MAAM,GAAG,IAAI,IAAI,GAAG,KAAK;AAClC;AAEO,SAAS,OAAO,OAA6B,aAAuD;AACzG,MAAI,CAAC,SAAS,CAAC,eAAe,MAAM,gBAAgB,KAAM,QAAO;AACjE,SAAO,MAAM,eAAe,cAAc,MAAM;AAClD;AAEO,SAAS,aAAa,OAA6B,aAAuD;AAC/G,QAAM,IAAI,OAAO,OAAO,WAAW;AACnC,SAAO,KAAK,OAAO,OAAO,IAAI,WAAW,CAAC,CAAC;AAC7C;","names":[]}
1
+ {"version":3,"sources":["../../src/data/coins.ts"],"sourcesContent":["\n\nimport { getService, listServices, type ServiceDefinition } from \"@medialane/sdk\";\nimport { formatSmallDecimal } from \"../utils/format.js\";\n\nexport type CoinKind = \"creator\" | \"memecoin\";\n\nexport interface CoinCollectionLike {\n contractAddress: string;\n chain?: string | null;\n name?: string | null;\n symbol?: string | null;\n image?: string | null;\n service?: string | null;\n claimedBy?: string | null;\n holderCount?: number | null;\n\n totalSupply?: string | null;\n decimals?: number | null;\n profile?: { image?: string | null } | null;\n}\n\nexport interface CoinPriceLike {\n quotePerCoin: number;\n quoteSymbol: string | null;\n /** USD value of one unit of quoteSymbol, when known — lets price\n * displays show a fiat-equivalent alongside the on-chain quote. */\n quoteUsdRate?: number | null;\n}\n\nexport function coinKind(service: string | null | undefined): CoinKind {\n return getService(service)?.provenance === \"EXTERNAL\" ? \"memecoin\" : \"creator\";\n}\n\nexport function isCoinService(def: ServiceDefinition): boolean {\n return def.uiVariant === \"coin\";\n}\n\nexport function coinServiceIds(kind: CoinKind): string[] {\n const provenance = kind === \"creator\" ? \"MEDIALANE\" : \"EXTERNAL\";\n return listServices()\n .filter((s) => isCoinService(s) && s.provenance === provenance)\n .map((s) => s.id);\n}\n\nexport function formatCoinPrice(n: number): string {\n return formatSmallDecimal(n);\n}\n\nconst ACCENT_TOKENS = [\n \"bg-brand-rose\",\n \"bg-brand-maeve\",\n \"bg-brand-purple\",\n \"bg-brand-orange\",\n \"bg-brand-blue\",\n] as const;\n\nexport function coinAccentToken(seed: string | null | undefined): string {\n const s = (seed ?? \"?\").trim().toUpperCase();\n let h = 0;\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0;\n return ACCENT_TOKENS[h % ACCENT_TOKENS.length];\n}\n\nfunction abbreviate(n: number): string {\n if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}B`;\n if (n >= 1_000_000) return `${(n / 1_000_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}M`;\n if (n >= 1_000) return `${(n / 1_000).toLocaleString(undefined, { maximumFractionDigits: 1 })}K`;\n return n.toLocaleString(undefined, { maximumFractionDigits: 2 });\n}\n\nexport function coinSupply(collection: CoinCollectionLike): number | null {\n const raw = collection.totalSupply;\n if (raw == null || raw === \"\") return null;\n let units: bigint;\n try {\n units = BigInt(raw);\n } catch {\n return null;\n }\n if (units <= 0n) return null;\n const supply = Number(units) / 10 ** (collection.decimals ?? 18);\n\n return isFinite(supply) && supply >= 1 ? supply : null;\n}\n\nexport function fdvUsd(price: CoinPriceLike | null, collection: CoinCollectionLike): number | null {\n const supply = coinSupply(collection);\n if (!price || supply == null || price.quoteUsdRate == null) return null;\n const v = price.quotePerCoin * supply * price.quoteUsdRate;\n return v > 0 && isFinite(v) ? v : null;\n}\n\nexport function formatFdvUsd(price: CoinPriceLike | null, collection: CoinCollectionLike): string | null {\n const v = fdvUsd(price, collection);\n return v == null ? null : `$${abbreviate(v)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,iBAAiE;AACjE,oBAAmC;AA2B5B,SAAS,SAAS,SAA8C;AACrE,aAAO,uBAAW,OAAO,GAAG,eAAe,aAAa,aAAa;AACvE;AAEO,SAAS,cAAc,KAAiC;AAC7D,SAAO,IAAI,cAAc;AAC3B;AAEO,SAAS,eAAe,MAA0B;AACvD,QAAM,aAAa,SAAS,YAAY,cAAc;AACtD,aAAO,yBAAa,EACjB,OAAO,CAAC,MAAM,cAAc,CAAC,KAAK,EAAE,eAAe,UAAU,EAC7D,IAAI,CAAC,MAAM,EAAE,EAAE;AACpB;AAEO,SAAS,gBAAgB,GAAmB;AACjD,aAAO,kCAAmB,CAAC;AAC7B;AAEA,MAAM,gBAAgB;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,gBAAgB,MAAyC;AACvE,QAAM,KAAK,QAAQ,KAAK,KAAK,EAAE,YAAY;AAC3C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,KAAK,IAAI,KAAK,EAAE,WAAW,CAAC,MAAO;AACtE,SAAO,cAAc,IAAI,cAAc,MAAM;AAC/C;AAEA,SAAS,WAAW,GAAmB;AACrC,MAAI,KAAK,IAAe,QAAO,IAAI,IAAI,KAAe,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC7G,MAAI,KAAK,IAAW,QAAO,IAAI,IAAI,KAAW,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AACrG,MAAI,KAAK,IAAO,QAAO,IAAI,IAAI,KAAO,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC7F,SAAO,EAAE,eAAe,QAAW,EAAE,uBAAuB,EAAE,CAAC;AACjE;AAEO,SAAS,WAAW,YAA+C;AACxE,QAAM,MAAM,WAAW;AACvB,MAAI,OAAO,QAAQ,QAAQ,GAAI,QAAO;AACtC,MAAI;AACJ,MAAI;AACF,YAAQ,OAAO,GAAG;AAAA,EACpB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,SAAS,GAAI,QAAO;AACxB,QAAM,SAAS,OAAO,KAAK,IAAI,OAAO,WAAW,YAAY;AAE7D,SAAO,SAAS,MAAM,KAAK,UAAU,IAAI,SAAS;AACpD;AAEO,SAAS,OAAO,OAA6B,YAA+C;AACjG,QAAM,SAAS,WAAW,UAAU;AACpC,MAAI,CAAC,SAAS,UAAU,QAAQ,MAAM,gBAAgB,KAAM,QAAO;AACnE,QAAM,IAAI,MAAM,eAAe,SAAS,MAAM;AAC9C,SAAO,IAAI,KAAK,SAAS,CAAC,IAAI,IAAI;AACpC;AAEO,SAAS,aAAa,OAA6B,YAA+C;AACvG,QAAM,IAAI,OAAO,OAAO,UAAU;AAClC,SAAO,KAAK,OAAO,OAAO,IAAI,WAAW,CAAC,CAAC;AAC7C;","names":[]}