@medialane/ui 0.99.0 → 0.100.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.
Files changed (169) hide show
  1. package/README.md +19 -0
  2. package/dist/components/action-button.cjs +1 -3
  3. package/dist/components/action-button.cjs.map +1 -1
  4. package/dist/components/action-button.js +1 -3
  5. package/dist/components/action-button.js.map +1 -1
  6. package/dist/components/activity-card.cjs +3 -3
  7. package/dist/components/activity-card.cjs.map +1 -1
  8. package/dist/components/activity-card.js +3 -3
  9. package/dist/components/activity-card.js.map +1 -1
  10. package/dist/components/activity-feed-shell.cjs +1 -1
  11. package/dist/components/activity-feed-shell.cjs.map +1 -1
  12. package/dist/components/activity-feed-shell.js +1 -1
  13. package/dist/components/activity-feed-shell.js.map +1 -1
  14. package/dist/components/activity-row.cjs +3 -3
  15. package/dist/components/activity-row.cjs.map +1 -1
  16. package/dist/components/activity-row.js +3 -3
  17. package/dist/components/activity-row.js.map +1 -1
  18. package/dist/components/activity-ticker.cjs +1 -1
  19. package/dist/components/activity-ticker.cjs.map +1 -1
  20. package/dist/components/activity-ticker.js +1 -1
  21. package/dist/components/activity-ticker.js.map +1 -1
  22. package/dist/components/asset-card.cjs +2 -2
  23. package/dist/components/asset-card.cjs.map +1 -1
  24. package/dist/components/asset-card.js +2 -2
  25. package/dist/components/asset-card.js.map +1 -1
  26. package/dist/components/asset-license-summary.cjs +2 -2
  27. package/dist/components/asset-license-summary.cjs.map +1 -1
  28. package/dist/components/asset-license-summary.js +2 -2
  29. package/dist/components/asset-license-summary.js.map +1 -1
  30. package/dist/components/asset-overview-content.cjs +2 -2
  31. package/dist/components/asset-overview-content.cjs.map +1 -1
  32. package/dist/components/asset-overview-content.js +2 -2
  33. package/dist/components/asset-overview-content.js.map +1 -1
  34. package/dist/components/asset-picker-cell.cjs +1 -1
  35. package/dist/components/asset-picker-cell.cjs.map +1 -1
  36. package/dist/components/asset-picker-cell.js +1 -1
  37. package/dist/components/asset-picker-cell.js.map +1 -1
  38. package/dist/components/asset-top-sections.cjs +1 -1
  39. package/dist/components/asset-top-sections.cjs.map +1 -1
  40. package/dist/components/asset-top-sections.js +1 -1
  41. package/dist/components/asset-top-sections.js.map +1 -1
  42. package/dist/components/asset-utility-icons.cjs +2 -2
  43. package/dist/components/asset-utility-icons.cjs.map +1 -1
  44. package/dist/components/asset-utility-icons.js +2 -2
  45. package/dist/components/asset-utility-icons.js.map +1 -1
  46. package/dist/components/claim-rail.cjs +1 -1
  47. package/dist/components/claim-rail.cjs.map +1 -1
  48. package/dist/components/claim-rail.js +1 -1
  49. package/dist/components/claim-rail.js.map +1 -1
  50. package/dist/components/coin-card.cjs +4 -4
  51. package/dist/components/coin-card.cjs.map +1 -1
  52. package/dist/components/coin-card.js +4 -4
  53. package/dist/components/coin-card.js.map +1 -1
  54. package/dist/components/coin-launch-preview.cjs +5 -5
  55. package/dist/components/coin-launch-preview.cjs.map +1 -1
  56. package/dist/components/coin-launch-preview.js +5 -5
  57. package/dist/components/coin-launch-preview.js.map +1 -1
  58. package/dist/components/coins-explorer.cjs +3 -3
  59. package/dist/components/coins-explorer.cjs.map +1 -1
  60. package/dist/components/coins-explorer.js +3 -3
  61. package/dist/components/coins-explorer.js.map +1 -1
  62. package/dist/components/collection-card.cjs +6 -6
  63. package/dist/components/collection-card.cjs.map +1 -1
  64. package/dist/components/collection-card.js +6 -6
  65. package/dist/components/collection-card.js.map +1 -1
  66. package/dist/components/collection-filters.cjs +5 -5
  67. package/dist/components/collection-filters.cjs.map +1 -1
  68. package/dist/components/collection-filters.js +5 -5
  69. package/dist/components/collection-filters.js.map +1 -1
  70. package/dist/components/community-rewards-section.cjs +2 -2
  71. package/dist/components/community-rewards-section.cjs.map +1 -1
  72. package/dist/components/community-rewards-section.js +2 -2
  73. package/dist/components/community-rewards-section.js.map +1 -1
  74. package/dist/components/discover-feed-section.cjs +1 -1
  75. package/dist/components/discover-feed-section.cjs.map +1 -1
  76. package/dist/components/discover-feed-section.js +1 -1
  77. package/dist/components/discover-feed-section.js.map +1 -1
  78. package/dist/components/featured-carousel.cjs +4 -4
  79. package/dist/components/featured-carousel.cjs.map +1 -1
  80. package/dist/components/featured-carousel.js +4 -4
  81. package/dist/components/featured-carousel.js.map +1 -1
  82. package/dist/components/gradient-button.cjs +1 -1
  83. package/dist/components/gradient-button.cjs.map +1 -1
  84. package/dist/components/gradient-button.js +1 -1
  85. package/dist/components/gradient-button.js.map +1 -1
  86. package/dist/components/hero-slider.cjs +2 -2
  87. package/dist/components/hero-slider.cjs.map +1 -1
  88. package/dist/components/hero-slider.js +2 -2
  89. package/dist/components/hero-slider.js.map +1 -1
  90. package/dist/components/launchpad-filter-bar.cjs +1 -1
  91. package/dist/components/launchpad-filter-bar.cjs.map +1 -1
  92. package/dist/components/launchpad-filter-bar.js +1 -1
  93. package/dist/components/launchpad-filter-bar.js.map +1 -1
  94. package/dist/components/launchpad-services.cjs +5 -5
  95. package/dist/components/launchpad-services.cjs.map +1 -1
  96. package/dist/components/launchpad-services.js +5 -5
  97. package/dist/components/launchpad-services.js.map +1 -1
  98. package/dist/components/launchpad-strip.cjs +4 -4
  99. package/dist/components/launchpad-strip.cjs.map +1 -1
  100. package/dist/components/launchpad-strip.js +4 -4
  101. package/dist/components/launchpad-strip.js.map +1 -1
  102. package/dist/components/listing-card.cjs +7 -7
  103. package/dist/components/listing-card.cjs.map +1 -1
  104. package/dist/components/listing-card.js +7 -7
  105. package/dist/components/listing-card.js.map +1 -1
  106. package/dist/components/medialane-collection-card.cjs +2 -2
  107. package/dist/components/medialane-collection-card.cjs.map +1 -1
  108. package/dist/components/medialane-collection-card.js +2 -2
  109. package/dist/components/medialane-collection-card.js.map +1 -1
  110. package/dist/components/nav-command-menu.cjs +5 -5
  111. package/dist/components/nav-command-menu.cjs.map +1 -1
  112. package/dist/components/nav-command-menu.js +5 -5
  113. package/dist/components/nav-command-menu.js.map +1 -1
  114. package/dist/components/nav-shell.cjs +2 -2
  115. package/dist/components/nav-shell.cjs.map +1 -1
  116. package/dist/components/nav-shell.js +2 -2
  117. package/dist/components/nav-shell.js.map +1 -1
  118. package/dist/components/portfolio-chip-filter.cjs +1 -1
  119. package/dist/components/portfolio-chip-filter.cjs.map +1 -1
  120. package/dist/components/portfolio-chip-filter.js +1 -1
  121. package/dist/components/portfolio-chip-filter.js.map +1 -1
  122. package/dist/components/portfolio-nav.cjs +2 -2
  123. package/dist/components/portfolio-nav.cjs.map +1 -1
  124. package/dist/components/portfolio-nav.js +2 -2
  125. package/dist/components/portfolio-nav.js.map +1 -1
  126. package/dist/components/rewards/journey-path.cjs +2 -2
  127. package/dist/components/rewards/journey-path.cjs.map +1 -1
  128. package/dist/components/rewards/journey-path.js +2 -2
  129. package/dist/components/rewards/journey-path.js.map +1 -1
  130. package/dist/components/rewards/leaderboard-table.cjs +1 -1
  131. package/dist/components/rewards/leaderboard-table.cjs.map +1 -1
  132. package/dist/components/rewards/leaderboard-table.js +1 -1
  133. package/dist/components/rewards/leaderboard-table.js.map +1 -1
  134. package/dist/components/rewards/level-badge.cjs +1 -1
  135. package/dist/components/rewards/level-badge.cjs.map +1 -1
  136. package/dist/components/rewards/level-badge.js +1 -1
  137. package/dist/components/rewards/level-badge.js.map +1 -1
  138. package/dist/components/rewards/level-ladder.cjs +2 -2
  139. package/dist/components/rewards/level-ladder.cjs.map +1 -1
  140. package/dist/components/rewards/level-ladder.js +2 -2
  141. package/dist/components/rewards/level-ladder.js.map +1 -1
  142. package/dist/components/scroll-section.cjs +1 -1
  143. package/dist/components/scroll-section.cjs.map +1 -1
  144. package/dist/components/scroll-section.js +1 -1
  145. package/dist/components/scroll-section.js.map +1 -1
  146. package/dist/components/share-button.cjs +1 -1
  147. package/dist/components/share-button.cjs.map +1 -1
  148. package/dist/components/share-button.js +1 -1
  149. package/dist/components/share-button.js.map +1 -1
  150. package/dist/components/stat-tile.cjs +3 -3
  151. package/dist/components/stat-tile.cjs.map +1 -1
  152. package/dist/components/stat-tile.js +3 -3
  153. package/dist/components/stat-tile.js.map +1 -1
  154. package/dist/components/token-card.cjs +4 -4
  155. package/dist/components/token-card.cjs.map +1 -1
  156. package/dist/components/token-card.js +4 -4
  157. package/dist/components/token-card.js.map +1 -1
  158. package/dist/data/brand.cjs +0 -14
  159. package/dist/data/brand.cjs.map +1 -1
  160. package/dist/data/brand.d.cts +0 -14
  161. package/dist/data/brand.d.ts +0 -14
  162. package/dist/data/brand.js +0 -14
  163. package/dist/data/brand.js.map +1 -1
  164. package/dist/medialane.css +4 -0
  165. package/dist/preset/tailwind.cjs +16 -8
  166. package/dist/preset/tailwind.cjs.map +1 -1
  167. package/dist/preset/tailwind.js +16 -8
  168. package/dist/preset/tailwind.js.map +1 -1
  169. package/package.json +6 -3
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/coins-explorer.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * CoinsExplorer — the shared coin-discovery surface (chain-agnostic).\n *\n * Owns view/search/filter/sort UI only. The data source (`useCoins`), the price\n * read (`usePrice`), and the link target (`coinHref`) are injected, so each app\n * (and each chain) wires its own without forking this component.\n *\n * Type + sort live in a centered Filters dialog (the \"action focus\" pattern, and\n * matching the marketplace page); the header stays to two lines.\n */\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, LayoutGrid, List, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinCard, CoinRow, CoinCardSkeleton, type UseCoinPrice } from \"./coin-card.js\";\nimport 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 /** Build the link target for a coin (internal or per-chain trading app). */\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\n// Recency default — never raw swap volume (05 §11 anti-wash hygiene).\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 [view, setView] = useState<\"grid\" | \"table\">(\"grid\");\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 return (\n <div className=\"space-y-6\">\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 {/* Controls — search + filters + view, kept lean. */}\n <div className=\"space-y-3 border-b border-border/60 pb-3\">\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-[10px] font-semibold text-primary-foreground\">\n {filterCount}\n </span>\n )}\n </button>\n <div className=\"inline-flex shrink-0 rounded-lg border border-border p-0.5\">\n {([{ v: \"grid\", Icon: LayoutGrid }, { v: \"table\", Icon: List }] as const).map(({ v, Icon }) => (\n <button\n key={v}\n onClick={() => setView(v)}\n aria-label={v === \"grid\" ? \"Grid view\" : \"Table view\"}\n className={cn(\"rounded-md p-1.5 transition-colors\", view === v ? \"bg-primary/10 text-primary\" : \"text-muted-foreground hover:text-foreground\")}\n >\n <Icon className=\"h-4 w-4\" />\n </button>\n ))}\n </div>\n </div>\n\n {/* Active-filter chips */}\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && (\n <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>\n )}\n {sort !== \"recent\" && (\n <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>\n )}\n </div>\n )}\n </div>\n\n {isLoading && items.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 8 }).map((_, i) => <CoinCardSkeleton key={i} />)}\n </div>\n ) : items.length === 0 ? (\n <div className=\"rounded-xl border border-border/60 py-16 text-center text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </div>\n ) : view === \"table\" ? (\n <div className=\"space-y-2\">\n {items.map((c) => <CoinRow key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4\">\n {items.map((c) => <CoinCard key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n )}\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=\"card-base relative z-10 w-full max-w-sm space-y-5 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-[10px] 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\n/**\n * CoinsExplorer — the shared coin-discovery surface (chain-agnostic).\n *\n * Owns view/search/filter/sort UI only. The data source (`useCoins`), the price\n * read (`usePrice`), and the link target (`coinHref`) are injected, so each app\n * (and each chain) wires its own without forking this component.\n *\n * Type + sort live in a centered Filters dialog (the \"action focus\" pattern, and\n * matching the marketplace page); the header stays to two lines.\n */\n\nimport { useState, useMemo, useEffect } from \"react\";\nimport { Coins, LayoutGrid, List, Search, SlidersHorizontal, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { CoinCard, CoinRow, CoinCardSkeleton, type UseCoinPrice } from \"./coin-card.js\";\nimport 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 /** Build the link target for a coin (internal or per-chain trading app). */\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\n// Recency default — never raw swap volume (05 §11 anti-wash hygiene).\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 [view, setView] = useState<\"grid\" | \"table\">(\"grid\");\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 return (\n <div className=\"space-y-6\">\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 {/* Controls — search + filters + view, kept lean. */}\n <div className=\"space-y-3 border-b border-border/60 pb-3\">\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 className=\"inline-flex shrink-0 rounded-lg border border-border p-0.5\">\n {([{ v: \"grid\", Icon: LayoutGrid }, { v: \"table\", Icon: List }] as const).map(({ v, Icon }) => (\n <button\n key={v}\n onClick={() => setView(v)}\n aria-label={v === \"grid\" ? \"Grid view\" : \"Table view\"}\n className={cn(\"rounded-md p-1.5 transition-colors\", view === v ? \"bg-primary/10 text-primary\" : \"text-muted-foreground hover:text-foreground\")}\n >\n <Icon className=\"h-4 w-4\" />\n </button>\n ))}\n </div>\n </div>\n\n {/* Active-filter chips */}\n {filterCount > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filter !== \"all\" && (\n <Chip onClear={() => setFilter(\"all\")}>{filterLabel(filter)}</Chip>\n )}\n {sort !== \"recent\" && (\n <Chip onClear={() => setSort(\"recent\")}>{sortLabel(sort)}</Chip>\n )}\n </div>\n )}\n </div>\n\n {isLoading && items.length === 0 ? (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4\">\n {Array.from({ length: 8 }).map((_, i) => <CoinCardSkeleton key={i} />)}\n </div>\n ) : items.length === 0 ? (\n <div className=\"rounded-xl border border-border/60 py-16 text-center text-muted-foreground\">\n {query.trim() ? `No coins match \"${query.trim()}\".` : \"No coins yet.\"}\n </div>\n ) : view === \"table\" ? (\n <div className=\"space-y-2\">\n {items.map((c) => <CoinRow key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4\">\n {items.map((c) => <CoinCard key={`${c.chain}-${c.contractAddress}`} collection={c} usePrice={usePrice} href={coinHref(c)} />)}\n </div>\n )}\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=\"card-base relative z-10 w-full max-w-sm space-y-5 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":[]}
@@ -65,9 +65,9 @@ function CollectionCard({ collection, href, settingsHref, className }) {
65
65
  {
66
66
  href: settingsHref,
67
67
  onClick: (e) => e.stopPropagation(),
68
- className: "absolute top-2 right-2 z-10 h-7 w-7 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white hover:bg-black/60 transition-colors",
68
+ className: "absolute top-2 right-2 z-10 min-h-11 min-w-11 rounded-full flex items-center justify-center",
69
69
  "aria-label": "Collection settings",
70
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Settings2, { className: "h-3.5 w-3.5" })
70
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm text-white/70 hover:text-white hover:bg-black/60 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Settings2, { className: "h-3.5 w-3.5" }) })
71
71
  }
72
72
  ),
73
73
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_link.default, { href: href ?? `/collections/${collection.contractAddress}`, className: "block relative h-full", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative aspect-[3/4] w-full overflow-hidden bg-muted", children: [
@@ -83,7 +83,7 @@ function CollectionCard({ collection, href, settingsHref, className }) {
83
83
  }
84
84
  ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-8xl font-black text-white/10 select-none tracking-tighter", children: initial }) }),
85
85
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start", children: [
86
- !collection.name && collection.metadataStatus === "PENDING" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-1 text-[10px] text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
86
+ !collection.name && collection.metadataStatus === "PENDING" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "flex items-center gap-1 text-2xs text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
87
87
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "h-2.5 w-2.5 animate-spin" }),
88
88
  "Indexing\u2026"
89
89
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -95,16 +95,16 @@ function CollectionCard({ collection, href, settingsHref, className }) {
95
95
  }
96
96
  ),
97
97
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
98
- collection.totalSupply != null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-[10px] font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
98
+ collection.totalSupply != null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xs font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
99
99
  collection.totalSupply.toLocaleString(),
100
100
  " items"
101
101
  ] }),
102
- floor && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1 text-[10px] font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
102
+ floor && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "inline-flex items-center gap-1 text-2xs font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
103
103
  "Floor",
104
104
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_currency_icon.CurrencyIcon, { symbol: floor.symbol, size: 10 }),
105
105
  floor.amount
106
106
  ] }),
107
- hasExclusiveContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-flex items-center gap-1 text-[10px] font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5", children: "\u2726 Exclusive" })
107
+ hasExclusiveContent && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "inline-flex items-center gap-1 text-2xs font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5", children: "\u2726 Exclusive" })
108
108
  ] })
109
109
  ] })
110
110
  ] }) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/collection-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { Loader2, Settings2 } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport type { ApiCollection } from \"@medialane/sdk\";\n\n/** \"0.990000 USDC\" → { amount: \"0.99\", symbol: \"USDC\" } */\nfunction parseFloorPrice(floor: string | null | undefined): { amount: string; symbol: string } | null {\n if (!floor) return null;\n const formatted = formatDisplayPrice(floor);\n const parts = formatted.trim().split(/\\s+/);\n if (parts.length < 2) return null;\n const symbol = parts[parts.length - 1];\n const amount = parts.slice(0, -1).join(\" \");\n return { amount, symbol };\n}\n\n/** ui's pinned SDK lags the apps — extend structurally for newer fields. */\ntype CollectionWithProfile = ApiCollection & {\n profile?: { hasGatedContent?: boolean } | null;\n};\n\nexport interface CollectionCardProps {\n collection: ApiCollection;\n /** Override the card link destination. Defaults to /collections/:contractAddress. */\n href?: string;\n /** Shows settings gear icon linking to this path — used in portfolio pages */\n settingsHref?: string;\n className?: string;\n}\n\nexport function CollectionCard({ collection, href, settingsHref, className }: CollectionCardProps) {\n const [imgError, setImgError] = useState(false);\n const imageUrl = collection.image ? ipfsToHttp(collection.image) : null;\n const showImage = imageUrl && !imgError;\n const initial = (collection.name ?? collection.contractAddress).charAt(0).toUpperCase();\n const floor = parseFloorPrice(collection.floorPrice);\n const hasExclusiveContent = !!(collection as CollectionWithProfile).profile?.hasGatedContent;\n\n return (\n <div className={cn(hasExclusiveContent && \"card-exclusive-wrapper btn-border-animated\")}>\n <MotionCard className={cn(\"card-base group relative\", className)}>\n {settingsHref && (\n <Link\n href={settingsHref}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute top-2 right-2 z-10 h-7 w-7 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Collection settings\"\n >\n <Settings2 className=\"h-3.5 w-3.5\" />\n </Link>\n )}\n\n <Link href={href ?? `/collections/${collection.contractAddress}`} className=\"block relative h-full\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n {showImage ? (\n <Image\n src={imageUrl}\n alt={collection.name ?? \"Collection\"}\n fill\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n onError={() => setImgError(true)}\n unoptimized\n />\n ) : (\n <div className=\"absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center\">\n <span className=\"text-8xl font-black text-white/10 select-none tracking-tighter\">\n {initial}\n </span>\n </div>\n )}\n\n <div className=\"absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start\">\n {!collection.name && collection.metadataStatus === \"PENDING\" ? (\n <span className=\"flex items-center gap-1 text-[10px] text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n <Loader2 className=\"h-2.5 w-2.5 animate-spin\" />\n Indexing…\n </span>\n ) : (\n <p\n className=\"font-bold text-sm text-white leading-tight backdrop-blur-md bg-black/30 rounded-lg px-2.5 py-1 max-w-full truncate\"\n style={{ textShadow: \"0 1px 6px rgba(0,0,0,0.5)\" }}\n >\n {collection.name ?? \"Unnamed Collection\"}\n </p>\n )}\n\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {collection.totalSupply != null && (\n <span className=\"text-[10px] font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n {collection.totalSupply.toLocaleString()} items\n </span>\n )}\n {floor && (\n <span className=\"inline-flex items-center gap-1 text-[10px] font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n Floor\n <CurrencyIcon symbol={floor.symbol} size={10} />\n {floor.amount}\n </span>\n )}\n {hasExclusiveContent && (\n <span className=\"inline-flex items-center gap-1 text-[10px] font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5\">\n ✦ Exclusive\n </span>\n )}\n </div>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CollectionCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n <div className=\"absolute inset-0 animate-pulse bg-muted\" />\n <div className=\"absolute bottom-3 left-3 right-3 space-y-1.5\">\n <div className=\"h-4 w-2/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-1/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwDY;AAtDZ,mBAAyB;AACzB,kBAAiB;AACjB,mBAAkB;AAClB,0BAAmC;AACnC,gBAAmB;AACnB,kBAA2B;AAC3B,oBAAmC;AACnC,+BAA2B;AAC3B,2BAA6B;AAI7B,SAAS,gBAAgB,OAA6E;AACpG,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,gBAAY,kCAAmB,KAAK;AAC1C,QAAM,QAAQ,UAAU,KAAK,EAAE,MAAM,KAAK;AAC1C,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,SAAS,MAAM,MAAM,SAAS,CAAC;AACrC,QAAM,SAAS,MAAM,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AAC1C,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAgBO,SAAS,eAAe,EAAE,YAAY,MAAM,cAAc,UAAU,GAAwB;AACjG,QAAM,CAAC,UAAU,WAAW,QAAI,uBAAS,KAAK;AAC9C,QAAM,WAAW,WAAW,YAAQ,wBAAW,WAAW,KAAK,IAAI;AACnE,QAAM,YAAY,YAAY,CAAC;AAC/B,QAAM,WAAW,WAAW,QAAQ,WAAW,iBAAiB,OAAO,CAAC,EAAE,YAAY;AACtF,QAAM,QAAQ,gBAAgB,WAAW,UAAU;AACnD,QAAM,sBAAsB,CAAC,CAAE,WAAqC,SAAS;AAE7E,SACE,4CAAC,SAAI,eAAW,cAAG,uBAAuB,4CAA4C,GACpF,uDAAC,uCAAW,eAAW,cAAG,4BAA4B,SAAS,GAC5D;AAAA,oBACC;AAAA,MAAC,YAAAA;AAAA,MAAA;AAAA,QACC,MAAM;AAAA,QACN,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,QAClC,WAAU;AAAA,QACV,cAAW;AAAA,QAEX,sDAAC,iCAAU,WAAU,eAAc;AAAA;AAAA,IACrC;AAAA,IAGF,4CAAC,YAAAA,SAAA,EAAK,MAAM,QAAQ,gBAAgB,WAAW,eAAe,IAAI,WAAU,yBAC1E,uDAAC,SAAI,WAAU,yDACZ;AAAA,kBACC;AAAA,QAAC,aAAAC;AAAA,QAAA;AAAA,UACC,KAAK;AAAA,UACL,KAAK,WAAW,QAAQ;AAAA,UACxB,MAAI;AAAA,UACJ,WAAU;AAAA,UACV,SAAS,MAAM,YAAY,IAAI;AAAA,UAC/B,aAAW;AAAA;AAAA,MACb,IAEA,4CAAC,SAAI,WAAU,+HACb,sDAAC,UAAK,WAAU,kEACb,mBACH,GACF;AAAA,MAGF,6CAAC,SAAI,WAAU,gFACZ;AAAA,SAAC,WAAW,QAAQ,WAAW,mBAAmB,YACjD,6CAAC,UAAK,WAAU,2GACd;AAAA,sDAAC,+BAAQ,WAAU,4BAA2B;AAAA,UAAE;AAAA,WAElD,IAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,YAAY,4BAA4B;AAAA,YAEhD,qBAAW,QAAQ;AAAA;AAAA,QACtB;AAAA,QAGF,6CAAC,SAAI,WAAU,uCACZ;AAAA,qBAAW,eAAe,QACzB,6CAAC,UAAK,WAAU,+FACb;AAAA,uBAAW,YAAY,eAAe;AAAA,YAAE;AAAA,aAC3C;AAAA,UAED,SACC,6CAAC,UAAK,WAAU,4HAA2H;AAAA;AAAA,YAEzI,4CAAC,qCAAa,QAAQ,MAAM,QAAQ,MAAM,IAAI;AAAA,YAC7C,MAAM;AAAA,aACT;AAAA,UAED,uBACC,4CAAC,UAAK,WAAU,yHAAwH,8BAExI;AAAA,WAEJ;AAAA,SACF;AAAA,OACF,GACF;AAAA,KACF,GACF;AAEJ;AAEO,SAAS,yBAAyB;AACvC,SACE,4CAAC,SAAI,WAAU,6BACb,uDAAC,SAAI,WAAU,yDACb;AAAA,gDAAC,SAAI,WAAU,2CAA0C;AAAA,IACzD,6CAAC,SAAI,WAAU,gDACb;AAAA,kDAAC,SAAI,WAAU,6DAA4D;AAAA,MAC3E,4CAAC,SAAI,WAAU,6DAA4D;AAAA,OAC7E;AAAA,KACF,GACF;AAEJ;","names":["Link","Image"]}
1
+ {"version":3,"sources":["../../src/components/collection-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { Loader2, Settings2 } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport type { ApiCollection } from \"@medialane/sdk\";\n\n/** \"0.990000 USDC\" → { amount: \"0.99\", symbol: \"USDC\" } */\nfunction parseFloorPrice(floor: string | null | undefined): { amount: string; symbol: string } | null {\n if (!floor) return null;\n const formatted = formatDisplayPrice(floor);\n const parts = formatted.trim().split(/\\s+/);\n if (parts.length < 2) return null;\n const symbol = parts[parts.length - 1];\n const amount = parts.slice(0, -1).join(\" \");\n return { amount, symbol };\n}\n\n/** ui's pinned SDK lags the apps — extend structurally for newer fields. */\ntype CollectionWithProfile = ApiCollection & {\n profile?: { hasGatedContent?: boolean } | null;\n};\n\nexport interface CollectionCardProps {\n collection: ApiCollection;\n /** Override the card link destination. Defaults to /collections/:contractAddress. */\n href?: string;\n /** Shows settings gear icon linking to this path — used in portfolio pages */\n settingsHref?: string;\n className?: string;\n}\n\nexport function CollectionCard({ collection, href, settingsHref, className }: CollectionCardProps) {\n const [imgError, setImgError] = useState(false);\n const imageUrl = collection.image ? ipfsToHttp(collection.image) : null;\n const showImage = imageUrl && !imgError;\n const initial = (collection.name ?? collection.contractAddress).charAt(0).toUpperCase();\n const floor = parseFloorPrice(collection.floorPrice);\n const hasExclusiveContent = !!(collection as CollectionWithProfile).profile?.hasGatedContent;\n\n return (\n <div className={cn(hasExclusiveContent && \"card-exclusive-wrapper btn-border-animated\")}>\n <MotionCard className={cn(\"card-base group relative\", className)}>\n {settingsHref && (\n <Link\n href={settingsHref}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute top-2 right-2 z-10 min-h-11 min-w-11 rounded-full flex items-center justify-center\"\n aria-label=\"Collection settings\"\n >\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm text-white/70 hover:text-white hover:bg-black/60 transition-colors\">\n <Settings2 className=\"h-3.5 w-3.5\" />\n </span>\n </Link>\n )}\n\n <Link href={href ?? `/collections/${collection.contractAddress}`} className=\"block relative h-full\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n {showImage ? (\n <Image\n src={imageUrl}\n alt={collection.name ?? \"Collection\"}\n fill\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n onError={() => setImgError(true)}\n unoptimized\n />\n ) : (\n <div className=\"absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center\">\n <span className=\"text-8xl font-black text-white/10 select-none tracking-tighter\">\n {initial}\n </span>\n </div>\n )}\n\n <div className=\"absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start\">\n {!collection.name && collection.metadataStatus === \"PENDING\" ? (\n <span className=\"flex items-center gap-1 text-2xs text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n <Loader2 className=\"h-2.5 w-2.5 animate-spin\" />\n Indexing…\n </span>\n ) : (\n <p\n className=\"font-bold text-sm text-white leading-tight backdrop-blur-md bg-black/30 rounded-lg px-2.5 py-1 max-w-full truncate\"\n style={{ textShadow: \"0 1px 6px rgba(0,0,0,0.5)\" }}\n >\n {collection.name ?? \"Unnamed Collection\"}\n </p>\n )}\n\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {collection.totalSupply != null && (\n <span className=\"text-2xs font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n {collection.totalSupply.toLocaleString()} items\n </span>\n )}\n {floor && (\n <span className=\"inline-flex items-center gap-1 text-2xs font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n Floor\n <CurrencyIcon symbol={floor.symbol} size={10} />\n {floor.amount}\n </span>\n )}\n {hasExclusiveContent && (\n <span className=\"inline-flex items-center gap-1 text-2xs font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5\">\n ✦ Exclusive\n </span>\n )}\n </div>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CollectionCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n <div className=\"absolute inset-0 animate-pulse bg-muted\" />\n <div className=\"absolute bottom-3 left-3 right-3 space-y-1.5\">\n <div className=\"h-4 w-2/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-1/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["Link","Image"]}
@@ -31,9 +31,9 @@ function CollectionCard({ collection, href, settingsHref, className }) {
31
31
  {
32
32
  href: settingsHref,
33
33
  onClick: (e) => e.stopPropagation(),
34
- className: "absolute top-2 right-2 z-10 h-7 w-7 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white hover:bg-black/60 transition-colors",
34
+ className: "absolute top-2 right-2 z-10 min-h-11 min-w-11 rounded-full flex items-center justify-center",
35
35
  "aria-label": "Collection settings",
36
- children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" })
36
+ children: /* @__PURE__ */ jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm text-white/70 hover:text-white hover:bg-black/60 transition-colors", children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" }) })
37
37
  }
38
38
  ),
39
39
  /* @__PURE__ */ jsx(Link, { href: href ?? `/collections/${collection.contractAddress}`, className: "block relative h-full", children: /* @__PURE__ */ jsxs("div", { className: "relative aspect-[3/4] w-full overflow-hidden bg-muted", children: [
@@ -49,7 +49,7 @@ function CollectionCard({ collection, href, settingsHref, className }) {
49
49
  }
50
50
  ) : /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center", children: /* @__PURE__ */ jsx("span", { className: "text-8xl font-black text-white/10 select-none tracking-tighter", children: initial }) }),
51
51
  /* @__PURE__ */ jsxs("div", { className: "absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start", children: [
52
- !collection.name && collection.metadataStatus === "PENDING" ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-[10px] text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
52
+ !collection.name && collection.metadataStatus === "PENDING" ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-2xs text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
53
53
  /* @__PURE__ */ jsx(Loader2, { className: "h-2.5 w-2.5 animate-spin" }),
54
54
  "Indexing\u2026"
55
55
  ] }) : /* @__PURE__ */ jsx(
@@ -61,16 +61,16 @@ function CollectionCard({ collection, href, settingsHref, className }) {
61
61
  }
62
62
  ),
63
63
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
64
- collection.totalSupply != null && /* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
64
+ collection.totalSupply != null && /* @__PURE__ */ jsxs("span", { className: "text-2xs font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
65
65
  collection.totalSupply.toLocaleString(),
66
66
  " items"
67
67
  ] }),
68
- floor && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[10px] font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
68
+ floor && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-2xs font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5", children: [
69
69
  "Floor",
70
70
  /* @__PURE__ */ jsx(CurrencyIcon, { symbol: floor.symbol, size: 10 }),
71
71
  floor.amount
72
72
  ] }),
73
- hasExclusiveContent && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1 text-[10px] font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5", children: "\u2726 Exclusive" })
73
+ hasExclusiveContent && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center gap-1 text-2xs font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5", children: "\u2726 Exclusive" })
74
74
  ] })
75
75
  ] })
76
76
  ] }) })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/collection-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { Loader2, Settings2 } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport type { ApiCollection } from \"@medialane/sdk\";\n\n/** \"0.990000 USDC\" → { amount: \"0.99\", symbol: \"USDC\" } */\nfunction parseFloorPrice(floor: string | null | undefined): { amount: string; symbol: string } | null {\n if (!floor) return null;\n const formatted = formatDisplayPrice(floor);\n const parts = formatted.trim().split(/\\s+/);\n if (parts.length < 2) return null;\n const symbol = parts[parts.length - 1];\n const amount = parts.slice(0, -1).join(\" \");\n return { amount, symbol };\n}\n\n/** ui's pinned SDK lags the apps — extend structurally for newer fields. */\ntype CollectionWithProfile = ApiCollection & {\n profile?: { hasGatedContent?: boolean } | null;\n};\n\nexport interface CollectionCardProps {\n collection: ApiCollection;\n /** Override the card link destination. Defaults to /collections/:contractAddress. */\n href?: string;\n /** Shows settings gear icon linking to this path — used in portfolio pages */\n settingsHref?: string;\n className?: string;\n}\n\nexport function CollectionCard({ collection, href, settingsHref, className }: CollectionCardProps) {\n const [imgError, setImgError] = useState(false);\n const imageUrl = collection.image ? ipfsToHttp(collection.image) : null;\n const showImage = imageUrl && !imgError;\n const initial = (collection.name ?? collection.contractAddress).charAt(0).toUpperCase();\n const floor = parseFloorPrice(collection.floorPrice);\n const hasExclusiveContent = !!(collection as CollectionWithProfile).profile?.hasGatedContent;\n\n return (\n <div className={cn(hasExclusiveContent && \"card-exclusive-wrapper btn-border-animated\")}>\n <MotionCard className={cn(\"card-base group relative\", className)}>\n {settingsHref && (\n <Link\n href={settingsHref}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute top-2 right-2 z-10 h-7 w-7 rounded-full bg-black/40 backdrop-blur-sm flex items-center justify-center text-white/70 hover:text-white hover:bg-black/60 transition-colors\"\n aria-label=\"Collection settings\"\n >\n <Settings2 className=\"h-3.5 w-3.5\" />\n </Link>\n )}\n\n <Link href={href ?? `/collections/${collection.contractAddress}`} className=\"block relative h-full\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n {showImage ? (\n <Image\n src={imageUrl}\n alt={collection.name ?? \"Collection\"}\n fill\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n onError={() => setImgError(true)}\n unoptimized\n />\n ) : (\n <div className=\"absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center\">\n <span className=\"text-8xl font-black text-white/10 select-none tracking-tighter\">\n {initial}\n </span>\n </div>\n )}\n\n <div className=\"absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start\">\n {!collection.name && collection.metadataStatus === \"PENDING\" ? (\n <span className=\"flex items-center gap-1 text-[10px] text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n <Loader2 className=\"h-2.5 w-2.5 animate-spin\" />\n Indexing…\n </span>\n ) : (\n <p\n className=\"font-bold text-sm text-white leading-tight backdrop-blur-md bg-black/30 rounded-lg px-2.5 py-1 max-w-full truncate\"\n style={{ textShadow: \"0 1px 6px rgba(0,0,0,0.5)\" }}\n >\n {collection.name ?? \"Unnamed Collection\"}\n </p>\n )}\n\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {collection.totalSupply != null && (\n <span className=\"text-[10px] font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n {collection.totalSupply.toLocaleString()} items\n </span>\n )}\n {floor && (\n <span className=\"inline-flex items-center gap-1 text-[10px] font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n Floor\n <CurrencyIcon symbol={floor.symbol} size={10} />\n {floor.amount}\n </span>\n )}\n {hasExclusiveContent && (\n <span className=\"inline-flex items-center gap-1 text-[10px] font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5\">\n ✦ Exclusive\n </span>\n )}\n </div>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CollectionCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n <div className=\"absolute inset-0 animate-pulse bg-muted\" />\n <div className=\"absolute bottom-3 left-3 right-3 space-y-1.5\">\n <div className=\"h-4 w-2/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-1/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/collection-card.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport Link from \"next/link\";\nimport Image from \"next/image\";\nimport { Loader2, Settings2 } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport { ipfsToHttp } from \"../utils/ipfs.js\";\nimport { formatDisplayPrice } from \"../utils/format.js\";\nimport { MotionCard } from \"./motion-primitives.js\";\nimport { CurrencyIcon } from \"./currency-icon.js\";\nimport type { ApiCollection } from \"@medialane/sdk\";\n\n/** \"0.990000 USDC\" → { amount: \"0.99\", symbol: \"USDC\" } */\nfunction parseFloorPrice(floor: string | null | undefined): { amount: string; symbol: string } | null {\n if (!floor) return null;\n const formatted = formatDisplayPrice(floor);\n const parts = formatted.trim().split(/\\s+/);\n if (parts.length < 2) return null;\n const symbol = parts[parts.length - 1];\n const amount = parts.slice(0, -1).join(\" \");\n return { amount, symbol };\n}\n\n/** ui's pinned SDK lags the apps — extend structurally for newer fields. */\ntype CollectionWithProfile = ApiCollection & {\n profile?: { hasGatedContent?: boolean } | null;\n};\n\nexport interface CollectionCardProps {\n collection: ApiCollection;\n /** Override the card link destination. Defaults to /collections/:contractAddress. */\n href?: string;\n /** Shows settings gear icon linking to this path — used in portfolio pages */\n settingsHref?: string;\n className?: string;\n}\n\nexport function CollectionCard({ collection, href, settingsHref, className }: CollectionCardProps) {\n const [imgError, setImgError] = useState(false);\n const imageUrl = collection.image ? ipfsToHttp(collection.image) : null;\n const showImage = imageUrl && !imgError;\n const initial = (collection.name ?? collection.contractAddress).charAt(0).toUpperCase();\n const floor = parseFloorPrice(collection.floorPrice);\n const hasExclusiveContent = !!(collection as CollectionWithProfile).profile?.hasGatedContent;\n\n return (\n <div className={cn(hasExclusiveContent && \"card-exclusive-wrapper btn-border-animated\")}>\n <MotionCard className={cn(\"card-base group relative\", className)}>\n {settingsHref && (\n <Link\n href={settingsHref}\n onClick={(e) => e.stopPropagation()}\n className=\"absolute top-2 right-2 z-10 min-h-11 min-w-11 rounded-full flex items-center justify-center\"\n aria-label=\"Collection settings\"\n >\n <span className=\"flex h-7 w-7 items-center justify-center rounded-full bg-black/40 backdrop-blur-sm text-white/70 hover:text-white hover:bg-black/60 transition-colors\">\n <Settings2 className=\"h-3.5 w-3.5\" />\n </span>\n </Link>\n )}\n\n <Link href={href ?? `/collections/${collection.contractAddress}`} className=\"block relative h-full\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n {showImage ? (\n <Image\n src={imageUrl}\n alt={collection.name ?? \"Collection\"}\n fill\n className=\"object-cover transition-transform duration-500 group-hover:scale-105\"\n onError={() => setImgError(true)}\n unoptimized\n />\n ) : (\n <div className=\"absolute inset-0 bg-gradient-to-br from-brand-purple/30 via-brand-blue/20 to-brand-navy/40 flex items-center justify-center\">\n <span className=\"text-8xl font-black text-white/10 select-none tracking-tighter\">\n {initial}\n </span>\n </div>\n )}\n\n <div className=\"absolute bottom-0 left-0 right-0 px-3 pb-3 flex flex-col gap-1.5 items-start\">\n {!collection.name && collection.metadataStatus === \"PENDING\" ? (\n <span className=\"flex items-center gap-1 text-2xs text-white/60 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n <Loader2 className=\"h-2.5 w-2.5 animate-spin\" />\n Indexing…\n </span>\n ) : (\n <p\n className=\"font-bold text-sm text-white leading-tight backdrop-blur-md bg-black/30 rounded-lg px-2.5 py-1 max-w-full truncate\"\n style={{ textShadow: \"0 1px 6px rgba(0,0,0,0.5)\" }}\n >\n {collection.name ?? \"Unnamed Collection\"}\n </p>\n )}\n\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {collection.totalSupply != null && (\n <span className=\"text-2xs font-medium text-white/80 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n {collection.totalSupply.toLocaleString()} items\n </span>\n )}\n {floor && (\n <span className=\"inline-flex items-center gap-1 text-2xs font-bold text-white/90 backdrop-blur-md bg-black/30 rounded-full px-2 py-0.5\">\n Floor\n <CurrencyIcon symbol={floor.symbol} size={10} />\n {floor.amount}\n </span>\n )}\n {hasExclusiveContent && (\n <span className=\"inline-flex items-center gap-1 text-2xs font-bold text-white backdrop-blur-md bg-black/50 rounded-full px-2 py-0.5\">\n ✦ Exclusive\n </span>\n )}\n </div>\n </div>\n </div>\n </Link>\n </MotionCard>\n </div>\n );\n}\n\nexport function CollectionCardSkeleton() {\n return (\n <div className=\"card-base overflow-hidden\">\n <div className=\"relative aspect-[3/4] w-full overflow-hidden bg-muted\">\n <div className=\"absolute inset-0 animate-pulse bg-muted\" />\n <div className=\"absolute bottom-3 left-3 right-3 space-y-1.5\">\n <div className=\"h-4 w-2/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n <div className=\"h-3 w-1/3 rounded-md bg-muted-foreground/20 animate-pulse\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -96,7 +96,7 @@ function CollectionFiltersTrigger({
96
96
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { onClick: onOpen, className: TRIGGER_BTN, children: [
97
97
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.SlidersHorizontal, { className: "h-3.5 w-3.5" }),
98
98
  "Filters",
99
- totalActiveCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-4 min-w-4 rounded-full bg-primary text-[10px] font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1", children: totalActiveCount })
99
+ totalActiveCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "h-4 min-w-4 rounded-full bg-primary text-2xs font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1", children: totalActiveCount })
100
100
  ] }),
101
101
  !sortIsDefault && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
102
102
  "button",
@@ -104,7 +104,7 @@ function CollectionFiltersTrigger({
104
104
  onClick: onSortReset,
105
105
  className: "inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors",
106
106
  children: [
107
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground text-[11px]", children: "Sort:" }),
107
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-muted-foreground text-2xs", children: "Sort:" }),
108
108
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: SORT_OPTIONS.find((o) => o.value === sort)?.label }),
109
109
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.X, { className: "h-3 w-3 text-muted-foreground ml-0.5" })
110
110
  ]
@@ -116,7 +116,7 @@ function CollectionFiltersTrigger({
116
116
  onClick: () => onRemoveFilter(traitType, value),
117
117
  className: "inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors",
118
118
  children: [
119
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-muted-foreground text-[11px]", children: [
119
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-muted-foreground text-2xs", children: [
120
120
  traitType,
121
121
  ":"
122
122
  ] }),
@@ -136,7 +136,7 @@ function CollectionFiltersTrigger({
136
136
  )
137
137
  ] });
138
138
  }
139
- const PILL_BASE = "inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-[12px] font-medium transition-colors";
139
+ const PILL_BASE = "inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-xs font-medium transition-colors";
140
140
  const PILL_ON = "border-primary bg-primary text-primary-foreground";
141
141
  const PILL_OFF = "border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30";
142
142
  function CollectionFiltersBody({
@@ -173,7 +173,7 @@ function CollectionFiltersBody({
173
173
  i > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-px w-full bg-border mb-4" }),
174
174
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between mb-2", children: [
175
175
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm font-medium", children: section.traitType }),
176
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-[11px] text-muted-foreground", children: [
176
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-2xs text-muted-foreground", children: [
177
177
  section.values.length,
178
178
  " values"
179
179
  ] })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/collection-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { SlidersHorizontal, X, Check } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { ApiToken, CollectionTokensSort } from \"@medialane/sdk\";\n\n// Trait types that are per-token stamps rather than real categories — never\n// useful as a filter regardless of how many distinct values they have.\n// \"Registration\" is the Berne Convention registration date, near-unique per\n// mint; filtering by an exact date isn't a meaningful narrowing action.\nconst EXCLUDED_TRAIT_TYPES = new Set([\"registration\"]);\n\nexport const SORT_OPTIONS: { value: CollectionTokensSort; label: string }[] = [\n { value: \"recent\", label: \"Recent\" },\n { value: \"oldest\", label: \"Oldest\" },\n { value: \"name\", label: \"Name\" },\n { value: \"price\", label: \"Price\" },\n];\n\nexport interface TraitSection {\n traitType: string;\n values: { value: string; count: number }[];\n}\n\nexport function useCollectionFilters(\n tokens: ApiToken[],\n selected: Record<string, string[]>,\n onChange: (filters: Record<string, string[]>) => void,\n sort: CollectionTokensSort,\n onSortChange: (sort: CollectionTokensSort) => void,\n) {\n // Build trait map with value counts from loaded tokens, then drop any\n // trait type where every token shares the same single value (can never\n // narrow the result set) or that's a known per-token stamp, not a category.\n const traitSections: TraitSection[] = useMemo(() => {\n const map = new Map<string, Map<string, number>>();\n for (const token of tokens) {\n const attrs = Array.isArray(token.metadata?.attributes)\n ? (token.metadata.attributes as { trait_type?: string; value?: string }[])\n : [];\n for (const attr of attrs) {\n if (!attr.trait_type || attr.value == null) continue;\n if (EXCLUDED_TRAIT_TYPES.has(attr.trait_type.toLowerCase())) continue;\n if (!map.has(attr.trait_type)) map.set(attr.trait_type, new Map());\n const counts = map.get(attr.trait_type)!;\n const v = String(attr.value);\n counts.set(v, (counts.get(v) ?? 0) + 1);\n }\n }\n return Array.from(map.entries())\n .map(([traitType, counts]) => ({\n traitType,\n values: Array.from(counts.entries())\n .map(([value, count]) => ({ value, count }))\n .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)),\n }))\n .filter((section) => section.values.length >= 2);\n }, [tokens]);\n\n const activeEntries = Object.entries(selected).flatMap(([traitType, values]) =>\n values.map((value) => ({ traitType, value }))\n );\n const traitActiveCount = activeEntries.length;\n const sortIsDefault = sort === \"recent\";\n const totalActiveCount = traitActiveCount + (sortIsDefault ? 0 : 1);\n\n function toggleValue(traitType: string, value: string) {\n const current = selected[traitType] ?? [];\n const next = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n const nextSelected = { ...selected };\n if (next.length === 0) {\n delete nextSelected[traitType];\n } else {\n nextSelected[traitType] = next;\n }\n onChange(nextSelected);\n }\n\n function clearAll() {\n onChange({});\n onSortChange(\"recent\");\n }\n\n function removeFilter(traitType: string, value: string) {\n toggleValue(traitType, value);\n }\n\n return { traitSections, activeEntries, totalActiveCount, sortIsDefault, toggleValue, clearAll, removeFilter };\n}\n\nconst TRIGGER_BTN = \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-8 px-3 shrink-0\";\n\nexport interface CollectionFiltersTriggerProps {\n totalActiveCount: number;\n sortIsDefault: boolean;\n sort: CollectionTokensSort;\n activeEntries: { traitType: string; value: string }[];\n onOpen: () => void;\n onSortReset: () => void;\n onRemoveFilter: (traitType: string, value: string) => void;\n onClearAll: () => void;\n}\n\nexport function CollectionFiltersTrigger({\n totalActiveCount,\n sortIsDefault,\n sort,\n activeEntries,\n onOpen,\n onSortReset,\n onRemoveFilter,\n onClearAll,\n}: CollectionFiltersTriggerProps) {\n return (\n <div className=\"flex items-center justify-end gap-2 flex-wrap\">\n <button onClick={onOpen} className={TRIGGER_BTN}>\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {totalActiveCount > 0 && (\n <span className=\"h-4 min-w-4 rounded-full bg-primary text-[10px] font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1\">\n {totalActiveCount}\n </span>\n )}\n </button>\n\n {/* Sort pill — only shown when not the default */}\n {!sortIsDefault && (\n <button\n onClick={onSortReset}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-[11px]\">Sort:</span>\n <span>{SORT_OPTIONS.find((o) => o.value === sort)?.label}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n )}\n\n {/* Active trait pills — one per selected value */}\n {activeEntries.map(({ traitType, value }) => (\n <button\n key={`${traitType}:${value}`}\n onClick={() => onRemoveFilter(traitType, value)}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-[11px]\">{traitType}:</span>\n <span>{value}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n ))}\n\n {totalActiveCount > 1 && (\n <button\n onClick={onClearAll}\n className=\"text-xs text-muted-foreground hover:text-foreground underline-offset-2 hover:underline transition-colors\"\n >\n Clear all\n </button>\n )}\n </div>\n );\n}\n\nexport interface CollectionFiltersBodyProps {\n sort: CollectionTokensSort;\n onSortChange: (sort: CollectionTokensSort) => void;\n traitSections: TraitSection[];\n selected: Record<string, string[]>;\n onToggleValue: (traitType: string, value: string) => void;\n}\n\nconst PILL_BASE = \"inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-[12px] font-medium transition-colors\";\nconst PILL_ON = \"border-primary bg-primary text-primary-foreground\";\nconst PILL_OFF = \"border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30\";\n\nexport function CollectionFiltersBody({\n sort,\n onSortChange,\n traitSections,\n selected,\n onToggleValue,\n}: CollectionFiltersBodyProps) {\n return (\n <div className=\"flex-1 overflow-y-auto px-5 py-3 space-y-4\">\n {/* Sort section */}\n <div>\n <div className=\"mb-2\">\n <span className=\"text-sm font-medium\">Sort by</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {SORT_OPTIONS.map(({ value, label }) => {\n const isSelected = sort === value;\n return (\n <button\n key={value}\n onClick={() => onSortChange(value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{label}</span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Trait sections — always expanded (pruning already removed the noise) */}\n {traitSections.length > 0 && (\n <>\n <div className=\"h-px w-full bg-border\" />\n {traitSections.map((section, i) => {\n const activeValues = selected[section.traitType] ?? [];\n return (\n <div key={section.traitType}>\n {i > 0 && <div className=\"h-px w-full bg-border mb-4\" />}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium\">{section.traitType}</span>\n <span className=\"text-[11px] text-muted-foreground\">\n {section.values.length} values\n </span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {section.values.map(({ value, count }) => {\n const isSelected = activeValues.includes(value);\n return (\n <button\n key={value}\n onClick={() => onToggleValue(section.traitType, value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{value}</span>\n <span className={isSelected ? \"opacity-80\" : \"opacity-60\"}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/collection-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { SlidersHorizontal, X, Check } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { ApiToken, CollectionTokensSort } from \"@medialane/sdk\";\n\n// Trait types that are per-token stamps rather than real categories — never\n// useful as a filter regardless of how many distinct values they have.\n// \"Registration\" is the Berne Convention registration date, near-unique per\n// mint; filtering by an exact date isn't a meaningful narrowing action.\nconst EXCLUDED_TRAIT_TYPES = new Set([\"registration\"]);\n\nexport const SORT_OPTIONS: { value: CollectionTokensSort; label: string }[] = [\n { value: \"recent\", label: \"Recent\" },\n { value: \"oldest\", label: \"Oldest\" },\n { value: \"name\", label: \"Name\" },\n { value: \"price\", label: \"Price\" },\n];\n\nexport interface TraitSection {\n traitType: string;\n values: { value: string; count: number }[];\n}\n\nexport function useCollectionFilters(\n tokens: ApiToken[],\n selected: Record<string, string[]>,\n onChange: (filters: Record<string, string[]>) => void,\n sort: CollectionTokensSort,\n onSortChange: (sort: CollectionTokensSort) => void,\n) {\n // Build trait map with value counts from loaded tokens, then drop any\n // trait type where every token shares the same single value (can never\n // narrow the result set) or that's a known per-token stamp, not a category.\n const traitSections: TraitSection[] = useMemo(() => {\n const map = new Map<string, Map<string, number>>();\n for (const token of tokens) {\n const attrs = Array.isArray(token.metadata?.attributes)\n ? (token.metadata.attributes as { trait_type?: string; value?: string }[])\n : [];\n for (const attr of attrs) {\n if (!attr.trait_type || attr.value == null) continue;\n if (EXCLUDED_TRAIT_TYPES.has(attr.trait_type.toLowerCase())) continue;\n if (!map.has(attr.trait_type)) map.set(attr.trait_type, new Map());\n const counts = map.get(attr.trait_type)!;\n const v = String(attr.value);\n counts.set(v, (counts.get(v) ?? 0) + 1);\n }\n }\n return Array.from(map.entries())\n .map(([traitType, counts]) => ({\n traitType,\n values: Array.from(counts.entries())\n .map(([value, count]) => ({ value, count }))\n .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)),\n }))\n .filter((section) => section.values.length >= 2);\n }, [tokens]);\n\n const activeEntries = Object.entries(selected).flatMap(([traitType, values]) =>\n values.map((value) => ({ traitType, value }))\n );\n const traitActiveCount = activeEntries.length;\n const sortIsDefault = sort === \"recent\";\n const totalActiveCount = traitActiveCount + (sortIsDefault ? 0 : 1);\n\n function toggleValue(traitType: string, value: string) {\n const current = selected[traitType] ?? [];\n const next = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n const nextSelected = { ...selected };\n if (next.length === 0) {\n delete nextSelected[traitType];\n } else {\n nextSelected[traitType] = next;\n }\n onChange(nextSelected);\n }\n\n function clearAll() {\n onChange({});\n onSortChange(\"recent\");\n }\n\n function removeFilter(traitType: string, value: string) {\n toggleValue(traitType, value);\n }\n\n return { traitSections, activeEntries, totalActiveCount, sortIsDefault, toggleValue, clearAll, removeFilter };\n}\n\nconst TRIGGER_BTN = \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-8 px-3 shrink-0\";\n\nexport interface CollectionFiltersTriggerProps {\n totalActiveCount: number;\n sortIsDefault: boolean;\n sort: CollectionTokensSort;\n activeEntries: { traitType: string; value: string }[];\n onOpen: () => void;\n onSortReset: () => void;\n onRemoveFilter: (traitType: string, value: string) => void;\n onClearAll: () => void;\n}\n\nexport function CollectionFiltersTrigger({\n totalActiveCount,\n sortIsDefault,\n sort,\n activeEntries,\n onOpen,\n onSortReset,\n onRemoveFilter,\n onClearAll,\n}: CollectionFiltersTriggerProps) {\n return (\n <div className=\"flex items-center justify-end gap-2 flex-wrap\">\n <button onClick={onOpen} className={TRIGGER_BTN}>\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {totalActiveCount > 0 && (\n <span className=\"h-4 min-w-4 rounded-full bg-primary text-2xs font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1\">\n {totalActiveCount}\n </span>\n )}\n </button>\n\n {/* Sort pill — only shown when not the default */}\n {!sortIsDefault && (\n <button\n onClick={onSortReset}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-2xs\">Sort:</span>\n <span>{SORT_OPTIONS.find((o) => o.value === sort)?.label}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n )}\n\n {/* Active trait pills — one per selected value */}\n {activeEntries.map(({ traitType, value }) => (\n <button\n key={`${traitType}:${value}`}\n onClick={() => onRemoveFilter(traitType, value)}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-2xs\">{traitType}:</span>\n <span>{value}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n ))}\n\n {totalActiveCount > 1 && (\n <button\n onClick={onClearAll}\n className=\"text-xs text-muted-foreground hover:text-foreground underline-offset-2 hover:underline transition-colors\"\n >\n Clear all\n </button>\n )}\n </div>\n );\n}\n\nexport interface CollectionFiltersBodyProps {\n sort: CollectionTokensSort;\n onSortChange: (sort: CollectionTokensSort) => void;\n traitSections: TraitSection[];\n selected: Record<string, string[]>;\n onToggleValue: (traitType: string, value: string) => void;\n}\n\nconst PILL_BASE = \"inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-xs font-medium transition-colors\";\nconst PILL_ON = \"border-primary bg-primary text-primary-foreground\";\nconst PILL_OFF = \"border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30\";\n\nexport function CollectionFiltersBody({\n sort,\n onSortChange,\n traitSections,\n selected,\n onToggleValue,\n}: CollectionFiltersBodyProps) {\n return (\n <div className=\"flex-1 overflow-y-auto px-5 py-3 space-y-4\">\n {/* Sort section */}\n <div>\n <div className=\"mb-2\">\n <span className=\"text-sm font-medium\">Sort by</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {SORT_OPTIONS.map(({ value, label }) => {\n const isSelected = sort === value;\n return (\n <button\n key={value}\n onClick={() => onSortChange(value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{label}</span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Trait sections — always expanded (pruning already removed the noise) */}\n {traitSections.length > 0 && (\n <>\n <div className=\"h-px w-full bg-border\" />\n {traitSections.map((section, i) => {\n const activeValues = selected[section.traitType] ?? [];\n return (\n <div key={section.traitType}>\n {i > 0 && <div className=\"h-px w-full bg-border mb-4\" />}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium\">{section.traitType}</span>\n <span className=\"text-2xs text-muted-foreground\">\n {section.values.length} values\n </span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {section.values.map(({ value, count }) => {\n const isSelected = activeValues.includes(value);\n return (\n <button\n key={value}\n onClick={() => onToggleValue(section.traitType, value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{value}</span>\n <span className={isSelected ? \"opacity-80\" : \"opacity-60\"}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -70,7 +70,7 @@ function CollectionFiltersTrigger({
70
70
  /* @__PURE__ */ jsxs("button", { onClick: onOpen, className: TRIGGER_BTN, children: [
71
71
  /* @__PURE__ */ jsx(SlidersHorizontal, { className: "h-3.5 w-3.5" }),
72
72
  "Filters",
73
- totalActiveCount > 0 && /* @__PURE__ */ jsx("span", { className: "h-4 min-w-4 rounded-full bg-primary text-[10px] font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1", children: totalActiveCount })
73
+ totalActiveCount > 0 && /* @__PURE__ */ jsx("span", { className: "h-4 min-w-4 rounded-full bg-primary text-2xs font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1", children: totalActiveCount })
74
74
  ] }),
75
75
  !sortIsDefault && /* @__PURE__ */ jsxs(
76
76
  "button",
@@ -78,7 +78,7 @@ function CollectionFiltersTrigger({
78
78
  onClick: onSortReset,
79
79
  className: "inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors",
80
80
  children: [
81
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-[11px]", children: "Sort:" }),
81
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-2xs", children: "Sort:" }),
82
82
  /* @__PURE__ */ jsx("span", { children: SORT_OPTIONS.find((o) => o.value === sort)?.label }),
83
83
  /* @__PURE__ */ jsx(X, { className: "h-3 w-3 text-muted-foreground ml-0.5" })
84
84
  ]
@@ -90,7 +90,7 @@ function CollectionFiltersTrigger({
90
90
  onClick: () => onRemoveFilter(traitType, value),
91
91
  className: "inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors",
92
92
  children: [
93
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-[11px]", children: [
93
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-2xs", children: [
94
94
  traitType,
95
95
  ":"
96
96
  ] }),
@@ -110,7 +110,7 @@ function CollectionFiltersTrigger({
110
110
  )
111
111
  ] });
112
112
  }
113
- const PILL_BASE = "inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-[12px] font-medium transition-colors";
113
+ const PILL_BASE = "inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-xs font-medium transition-colors";
114
114
  const PILL_ON = "border-primary bg-primary text-primary-foreground";
115
115
  const PILL_OFF = "border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30";
116
116
  function CollectionFiltersBody({
@@ -147,7 +147,7 @@ function CollectionFiltersBody({
147
147
  i > 0 && /* @__PURE__ */ jsx("div", { className: "h-px w-full bg-border mb-4" }),
148
148
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-2", children: [
149
149
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: section.traitType }),
150
- /* @__PURE__ */ jsxs("span", { className: "text-[11px] text-muted-foreground", children: [
150
+ /* @__PURE__ */ jsxs("span", { className: "text-2xs text-muted-foreground", children: [
151
151
  section.values.length,
152
152
  " values"
153
153
  ] })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/collection-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { SlidersHorizontal, X, Check } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { ApiToken, CollectionTokensSort } from \"@medialane/sdk\";\n\n// Trait types that are per-token stamps rather than real categories — never\n// useful as a filter regardless of how many distinct values they have.\n// \"Registration\" is the Berne Convention registration date, near-unique per\n// mint; filtering by an exact date isn't a meaningful narrowing action.\nconst EXCLUDED_TRAIT_TYPES = new Set([\"registration\"]);\n\nexport const SORT_OPTIONS: { value: CollectionTokensSort; label: string }[] = [\n { value: \"recent\", label: \"Recent\" },\n { value: \"oldest\", label: \"Oldest\" },\n { value: \"name\", label: \"Name\" },\n { value: \"price\", label: \"Price\" },\n];\n\nexport interface TraitSection {\n traitType: string;\n values: { value: string; count: number }[];\n}\n\nexport function useCollectionFilters(\n tokens: ApiToken[],\n selected: Record<string, string[]>,\n onChange: (filters: Record<string, string[]>) => void,\n sort: CollectionTokensSort,\n onSortChange: (sort: CollectionTokensSort) => void,\n) {\n // Build trait map with value counts from loaded tokens, then drop any\n // trait type where every token shares the same single value (can never\n // narrow the result set) or that's a known per-token stamp, not a category.\n const traitSections: TraitSection[] = useMemo(() => {\n const map = new Map<string, Map<string, number>>();\n for (const token of tokens) {\n const attrs = Array.isArray(token.metadata?.attributes)\n ? (token.metadata.attributes as { trait_type?: string; value?: string }[])\n : [];\n for (const attr of attrs) {\n if (!attr.trait_type || attr.value == null) continue;\n if (EXCLUDED_TRAIT_TYPES.has(attr.trait_type.toLowerCase())) continue;\n if (!map.has(attr.trait_type)) map.set(attr.trait_type, new Map());\n const counts = map.get(attr.trait_type)!;\n const v = String(attr.value);\n counts.set(v, (counts.get(v) ?? 0) + 1);\n }\n }\n return Array.from(map.entries())\n .map(([traitType, counts]) => ({\n traitType,\n values: Array.from(counts.entries())\n .map(([value, count]) => ({ value, count }))\n .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)),\n }))\n .filter((section) => section.values.length >= 2);\n }, [tokens]);\n\n const activeEntries = Object.entries(selected).flatMap(([traitType, values]) =>\n values.map((value) => ({ traitType, value }))\n );\n const traitActiveCount = activeEntries.length;\n const sortIsDefault = sort === \"recent\";\n const totalActiveCount = traitActiveCount + (sortIsDefault ? 0 : 1);\n\n function toggleValue(traitType: string, value: string) {\n const current = selected[traitType] ?? [];\n const next = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n const nextSelected = { ...selected };\n if (next.length === 0) {\n delete nextSelected[traitType];\n } else {\n nextSelected[traitType] = next;\n }\n onChange(nextSelected);\n }\n\n function clearAll() {\n onChange({});\n onSortChange(\"recent\");\n }\n\n function removeFilter(traitType: string, value: string) {\n toggleValue(traitType, value);\n }\n\n return { traitSections, activeEntries, totalActiveCount, sortIsDefault, toggleValue, clearAll, removeFilter };\n}\n\nconst TRIGGER_BTN = \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-8 px-3 shrink-0\";\n\nexport interface CollectionFiltersTriggerProps {\n totalActiveCount: number;\n sortIsDefault: boolean;\n sort: CollectionTokensSort;\n activeEntries: { traitType: string; value: string }[];\n onOpen: () => void;\n onSortReset: () => void;\n onRemoveFilter: (traitType: string, value: string) => void;\n onClearAll: () => void;\n}\n\nexport function CollectionFiltersTrigger({\n totalActiveCount,\n sortIsDefault,\n sort,\n activeEntries,\n onOpen,\n onSortReset,\n onRemoveFilter,\n onClearAll,\n}: CollectionFiltersTriggerProps) {\n return (\n <div className=\"flex items-center justify-end gap-2 flex-wrap\">\n <button onClick={onOpen} className={TRIGGER_BTN}>\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {totalActiveCount > 0 && (\n <span className=\"h-4 min-w-4 rounded-full bg-primary text-[10px] font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1\">\n {totalActiveCount}\n </span>\n )}\n </button>\n\n {/* Sort pill — only shown when not the default */}\n {!sortIsDefault && (\n <button\n onClick={onSortReset}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-[11px]\">Sort:</span>\n <span>{SORT_OPTIONS.find((o) => o.value === sort)?.label}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n )}\n\n {/* Active trait pills — one per selected value */}\n {activeEntries.map(({ traitType, value }) => (\n <button\n key={`${traitType}:${value}`}\n onClick={() => onRemoveFilter(traitType, value)}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-[11px]\">{traitType}:</span>\n <span>{value}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n ))}\n\n {totalActiveCount > 1 && (\n <button\n onClick={onClearAll}\n className=\"text-xs text-muted-foreground hover:text-foreground underline-offset-2 hover:underline transition-colors\"\n >\n Clear all\n </button>\n )}\n </div>\n );\n}\n\nexport interface CollectionFiltersBodyProps {\n sort: CollectionTokensSort;\n onSortChange: (sort: CollectionTokensSort) => void;\n traitSections: TraitSection[];\n selected: Record<string, string[]>;\n onToggleValue: (traitType: string, value: string) => void;\n}\n\nconst PILL_BASE = \"inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-[12px] font-medium transition-colors\";\nconst PILL_ON = \"border-primary bg-primary text-primary-foreground\";\nconst PILL_OFF = \"border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30\";\n\nexport function CollectionFiltersBody({\n sort,\n onSortChange,\n traitSections,\n selected,\n onToggleValue,\n}: CollectionFiltersBodyProps) {\n return (\n <div className=\"flex-1 overflow-y-auto px-5 py-3 space-y-4\">\n {/* Sort section */}\n <div>\n <div className=\"mb-2\">\n <span className=\"text-sm font-medium\">Sort by</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {SORT_OPTIONS.map(({ value, label }) => {\n const isSelected = sort === value;\n return (\n <button\n key={value}\n onClick={() => onSortChange(value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{label}</span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Trait sections — always expanded (pruning already removed the noise) */}\n {traitSections.length > 0 && (\n <>\n <div className=\"h-px w-full bg-border\" />\n {traitSections.map((section, i) => {\n const activeValues = selected[section.traitType] ?? [];\n return (\n <div key={section.traitType}>\n {i > 0 && <div className=\"h-px w-full bg-border mb-4\" />}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium\">{section.traitType}</span>\n <span className=\"text-[11px] text-muted-foreground\">\n {section.values.length} values\n </span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {section.values.map(({ value, count }) => {\n const isSelected = activeValues.includes(value);\n return (\n <button\n key={value}\n onClick={() => onToggleValue(section.traitType, value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{value}</span>\n <span className={isSelected ? \"opacity-80\" : \"opacity-60\"}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </>\n )}\n </div>\n );\n}\n"],"mappings":";AAsHM,SA4FE,UA3FA,KADF;AApHN,SAAS,eAAe;AACxB,SAAS,mBAAmB,GAAG,aAAa;AAC5C,SAAS,UAAU;AAOnB,MAAM,uBAAuB,oBAAI,IAAI,CAAC,cAAc,CAAC;AAE9C,MAAM,eAAiE;AAAA,EAC5E,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,EACnC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,EAC/B,EAAE,OAAO,SAAS,OAAO,QAAQ;AACnC;AAOO,SAAS,qBACd,QACA,UACA,UACA,MACA,cACA;AAIA,QAAM,gBAAgC,QAAQ,MAAM;AAClD,UAAM,MAAM,oBAAI,IAAiC;AACjD,eAAW,SAAS,QAAQ;AAC1B,YAAM,QAAQ,MAAM,QAAQ,MAAM,UAAU,UAAU,IACjD,MAAM,SAAS,aAChB,CAAC;AACL,iBAAW,QAAQ,OAAO;AACxB,YAAI,CAAC,KAAK,cAAc,KAAK,SAAS,KAAM;AAC5C,YAAI,qBAAqB,IAAI,KAAK,WAAW,YAAY,CAAC,EAAG;AAC7D,YAAI,CAAC,IAAI,IAAI,KAAK,UAAU,EAAG,KAAI,IAAI,KAAK,YAAY,oBAAI,IAAI,CAAC;AACjE,cAAM,SAAS,IAAI,IAAI,KAAK,UAAU;AACtC,cAAM,IAAI,OAAO,KAAK,KAAK;AAC3B,eAAO,IAAI,IAAI,OAAO,IAAI,CAAC,KAAK,KAAK,CAAC;AAAA,MACxC;AAAA,IACF;AACA,WAAO,MAAM,KAAK,IAAI,QAAQ,CAAC,EAC5B,IAAI,CAAC,CAAC,WAAW,MAAM,OAAO;AAAA,MAC7B;AAAA,MACA,QAAQ,MAAM,KAAK,OAAO,QAAQ,CAAC,EAChC,IAAI,CAAC,CAAC,OAAO,KAAK,OAAO,EAAE,OAAO,MAAM,EAAE,EAC1C,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAAA,IACvE,EAAE,EACD,OAAO,CAAC,YAAY,QAAQ,OAAO,UAAU,CAAC;AAAA,EACnD,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,gBAAgB,OAAO,QAAQ,QAAQ,EAAE;AAAA,IAAQ,CAAC,CAAC,WAAW,MAAM,MACxE,OAAO,IAAI,CAAC,WAAW,EAAE,WAAW,MAAM,EAAE;AAAA,EAC9C;AACA,QAAM,mBAAmB,cAAc;AACvC,QAAM,gBAAgB,SAAS;AAC/B,QAAM,mBAAmB,oBAAoB,gBAAgB,IAAI;AAEjE,WAAS,YAAY,WAAmB,OAAe;AACrD,UAAM,UAAU,SAAS,SAAS,KAAK,CAAC;AACxC,UAAM,OAAO,QAAQ,SAAS,KAAK,IAC/B,QAAQ,OAAO,CAAC,MAAM,MAAM,KAAK,IACjC,CAAC,GAAG,SAAS,KAAK;AACtB,UAAM,eAAe,EAAE,GAAG,SAAS;AACnC,QAAI,KAAK,WAAW,GAAG;AACrB,aAAO,aAAa,SAAS;AAAA,IAC/B,OAAO;AACL,mBAAa,SAAS,IAAI;AAAA,IAC5B;AACA,aAAS,YAAY;AAAA,EACvB;AAEA,WAAS,WAAW;AAClB,aAAS,CAAC,CAAC;AACX,iBAAa,QAAQ;AAAA,EACvB;AAEA,WAAS,aAAa,WAAmB,OAAe;AACtD,gBAAY,WAAW,KAAK;AAAA,EAC9B;AAEA,SAAO,EAAE,eAAe,eAAe,kBAAkB,eAAe,aAAa,UAAU,aAAa;AAC9G;AAEA,MAAM,cAAc;AAab,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,SACE,qBAAC,SAAI,WAAU,iDACb;AAAA,yBAAC,YAAO,SAAS,QAAQ,WAAW,aAClC;AAAA,0BAAC,qBAAkB,WAAU,eAAc;AAAA,MAAE;AAAA,MAE5C,mBAAmB,KAClB,oBAAC,UAAK,WAAU,iIACb,4BACH;AAAA,OAEJ;AAAA,IAGC,CAAC,iBACA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAU;AAAA,QAEV;AAAA,8BAAC,UAAK,WAAU,qCAAoC,mBAAK;AAAA,UACzD,oBAAC,UAAM,uBAAa,KAAK,CAAC,MAAM,EAAE,UAAU,IAAI,GAAG,OAAM;AAAA,UACzD,oBAAC,KAAE,WAAU,wCAAuC;AAAA;AAAA;AAAA,IACtD;AAAA,IAID,cAAc,IAAI,CAAC,EAAE,WAAW,MAAM,MACrC;AAAA,MAAC;AAAA;AAAA,QAEC,SAAS,MAAM,eAAe,WAAW,KAAK;AAAA,QAC9C,WAAU;AAAA,QAEV;AAAA,+BAAC,UAAK,WAAU,qCAAqC;AAAA;AAAA,YAAU;AAAA,aAAC;AAAA,UAChE,oBAAC,UAAM,iBAAM;AAAA,UACb,oBAAC,KAAE,WAAU,wCAAuC;AAAA;AAAA;AAAA,MAN/C,GAAG,SAAS,IAAI,KAAK;AAAA,IAO5B,CACD;AAAA,IAEA,mBAAmB,KAClB;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ;AAEJ;AAUA,MAAM,YAAY;AAClB,MAAM,UAAU;AAChB,MAAM,WAAW;AAEV,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA+B;AAC7B,SACE,qBAAC,SAAI,WAAU,8CAEb;AAAA,yBAAC,SACC;AAAA,0BAAC,SAAI,WAAU,QACb,8BAAC,UAAK,WAAU,uBAAsB,qBAAO,GAC/C;AAAA,MACA,oBAAC,SAAI,WAAU,0BACZ,uBAAa,IAAI,CAAC,EAAE,OAAO,MAAM,MAAM;AACtC,cAAM,aAAa,SAAS;AAC5B,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,SAAS,MAAM,aAAa,KAAK;AAAA,YACjC,WAAW,GAAG,WAAW,aAAa,UAAU,QAAQ;AAAA,YAEvD;AAAA,4BAAc,oBAAC,SAAM,WAAU,WAAU;AAAA,cAC1C,oBAAC,UAAM,iBAAM;AAAA;AAAA;AAAA,UALR;AAAA,QAMP;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IAGC,cAAc,SAAS,KACtB,iCACE;AAAA,0BAAC,SAAI,WAAU,yBAAwB;AAAA,MACtC,cAAc,IAAI,CAAC,SAAS,MAAM;AACjC,cAAM,eAAe,SAAS,QAAQ,SAAS,KAAK,CAAC;AACrD,eACE,qBAAC,SACE;AAAA,cAAI,KAAK,oBAAC,SAAI,WAAU,8BAA6B;AAAA,UACtD,qBAAC,SAAI,WAAU,0CACb;AAAA,gCAAC,UAAK,WAAU,uBAAuB,kBAAQ,WAAU;AAAA,YACzD,qBAAC,UAAK,WAAU,qCACb;AAAA,sBAAQ,OAAO;AAAA,cAAO;AAAA,eACzB;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,0BACZ,kBAAQ,OAAO,IAAI,CAAC,EAAE,OAAO,MAAM,MAAM;AACxC,kBAAM,aAAa,aAAa,SAAS,KAAK;AAC9C,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,MAAM,cAAc,QAAQ,WAAW,KAAK;AAAA,gBACrD,WAAW,GAAG,WAAW,aAAa,UAAU,QAAQ;AAAA,gBAEvD;AAAA,gCAAc,oBAAC,SAAM,WAAU,WAAU;AAAA,kBAC1C,oBAAC,UAAM,iBAAM;AAAA,kBACb,oBAAC,UAAK,WAAW,aAAa,eAAe,cAC1C,iBACH;AAAA;AAAA;AAAA,cARK;AAAA,YASP;AAAA,UAEJ,CAAC,GACH;AAAA,aAzBQ,QAAQ,SA0BlB;AAAA,MAEJ,CAAC;AAAA,OACH;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/collection-filters.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo } from \"react\";\nimport { SlidersHorizontal, X, Check } from \"lucide-react\";\nimport { cn } from \"../utils/cn.js\";\nimport type { ApiToken, CollectionTokensSort } from \"@medialane/sdk\";\n\n// Trait types that are per-token stamps rather than real categories — never\n// useful as a filter regardless of how many distinct values they have.\n// \"Registration\" is the Berne Convention registration date, near-unique per\n// mint; filtering by an exact date isn't a meaningful narrowing action.\nconst EXCLUDED_TRAIT_TYPES = new Set([\"registration\"]);\n\nexport const SORT_OPTIONS: { value: CollectionTokensSort; label: string }[] = [\n { value: \"recent\", label: \"Recent\" },\n { value: \"oldest\", label: \"Oldest\" },\n { value: \"name\", label: \"Name\" },\n { value: \"price\", label: \"Price\" },\n];\n\nexport interface TraitSection {\n traitType: string;\n values: { value: string; count: number }[];\n}\n\nexport function useCollectionFilters(\n tokens: ApiToken[],\n selected: Record<string, string[]>,\n onChange: (filters: Record<string, string[]>) => void,\n sort: CollectionTokensSort,\n onSortChange: (sort: CollectionTokensSort) => void,\n) {\n // Build trait map with value counts from loaded tokens, then drop any\n // trait type where every token shares the same single value (can never\n // narrow the result set) or that's a known per-token stamp, not a category.\n const traitSections: TraitSection[] = useMemo(() => {\n const map = new Map<string, Map<string, number>>();\n for (const token of tokens) {\n const attrs = Array.isArray(token.metadata?.attributes)\n ? (token.metadata.attributes as { trait_type?: string; value?: string }[])\n : [];\n for (const attr of attrs) {\n if (!attr.trait_type || attr.value == null) continue;\n if (EXCLUDED_TRAIT_TYPES.has(attr.trait_type.toLowerCase())) continue;\n if (!map.has(attr.trait_type)) map.set(attr.trait_type, new Map());\n const counts = map.get(attr.trait_type)!;\n const v = String(attr.value);\n counts.set(v, (counts.get(v) ?? 0) + 1);\n }\n }\n return Array.from(map.entries())\n .map(([traitType, counts]) => ({\n traitType,\n values: Array.from(counts.entries())\n .map(([value, count]) => ({ value, count }))\n .sort((a, b) => b.count - a.count || a.value.localeCompare(b.value)),\n }))\n .filter((section) => section.values.length >= 2);\n }, [tokens]);\n\n const activeEntries = Object.entries(selected).flatMap(([traitType, values]) =>\n values.map((value) => ({ traitType, value }))\n );\n const traitActiveCount = activeEntries.length;\n const sortIsDefault = sort === \"recent\";\n const totalActiveCount = traitActiveCount + (sortIsDefault ? 0 : 1);\n\n function toggleValue(traitType: string, value: string) {\n const current = selected[traitType] ?? [];\n const next = current.includes(value)\n ? current.filter((v) => v !== value)\n : [...current, value];\n const nextSelected = { ...selected };\n if (next.length === 0) {\n delete nextSelected[traitType];\n } else {\n nextSelected[traitType] = next;\n }\n onChange(nextSelected);\n }\n\n function clearAll() {\n onChange({});\n onSortChange(\"recent\");\n }\n\n function removeFilter(traitType: string, value: string) {\n toggleValue(traitType, value);\n }\n\n return { traitSections, activeEntries, totalActiveCount, sortIsDefault, toggleValue, clearAll, removeFilter };\n}\n\nconst TRIGGER_BTN = \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md border border-input bg-background text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground h-8 px-3 shrink-0\";\n\nexport interface CollectionFiltersTriggerProps {\n totalActiveCount: number;\n sortIsDefault: boolean;\n sort: CollectionTokensSort;\n activeEntries: { traitType: string; value: string }[];\n onOpen: () => void;\n onSortReset: () => void;\n onRemoveFilter: (traitType: string, value: string) => void;\n onClearAll: () => void;\n}\n\nexport function CollectionFiltersTrigger({\n totalActiveCount,\n sortIsDefault,\n sort,\n activeEntries,\n onOpen,\n onSortReset,\n onRemoveFilter,\n onClearAll,\n}: CollectionFiltersTriggerProps) {\n return (\n <div className=\"flex items-center justify-end gap-2 flex-wrap\">\n <button onClick={onOpen} className={TRIGGER_BTN}>\n <SlidersHorizontal className=\"h-3.5 w-3.5\" />\n Filters\n {totalActiveCount > 0 && (\n <span className=\"h-4 min-w-4 rounded-full bg-primary text-2xs font-bold text-primary-foreground flex items-center justify-center px-1 -mr-1\">\n {totalActiveCount}\n </span>\n )}\n </button>\n\n {/* Sort pill — only shown when not the default */}\n {!sortIsDefault && (\n <button\n onClick={onSortReset}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-2xs\">Sort:</span>\n <span>{SORT_OPTIONS.find((o) => o.value === sort)?.label}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n )}\n\n {/* Active trait pills — one per selected value */}\n {activeEntries.map(({ traitType, value }) => (\n <button\n key={`${traitType}:${value}`}\n onClick={() => onRemoveFilter(traitType, value)}\n className=\"inline-flex items-center gap-1 h-8 px-2.5 rounded-md border border-primary/40 bg-primary/10 text-xs font-medium text-foreground hover:bg-primary/20 transition-colors\"\n >\n <span className=\"text-muted-foreground text-2xs\">{traitType}:</span>\n <span>{value}</span>\n <X className=\"h-3 w-3 text-muted-foreground ml-0.5\" />\n </button>\n ))}\n\n {totalActiveCount > 1 && (\n <button\n onClick={onClearAll}\n className=\"text-xs text-muted-foreground hover:text-foreground underline-offset-2 hover:underline transition-colors\"\n >\n Clear all\n </button>\n )}\n </div>\n );\n}\n\nexport interface CollectionFiltersBodyProps {\n sort: CollectionTokensSort;\n onSortChange: (sort: CollectionTokensSort) => void;\n traitSections: TraitSection[];\n selected: Record<string, string[]>;\n onToggleValue: (traitType: string, value: string) => void;\n}\n\nconst PILL_BASE = \"inline-flex items-center gap-1 h-7 px-2.5 rounded-full border text-xs font-medium transition-colors\";\nconst PILL_ON = \"border-primary bg-primary text-primary-foreground\";\nconst PILL_OFF = \"border-border bg-transparent text-muted-foreground hover:text-foreground hover:border-foreground/30\";\n\nexport function CollectionFiltersBody({\n sort,\n onSortChange,\n traitSections,\n selected,\n onToggleValue,\n}: CollectionFiltersBodyProps) {\n return (\n <div className=\"flex-1 overflow-y-auto px-5 py-3 space-y-4\">\n {/* Sort section */}\n <div>\n <div className=\"mb-2\">\n <span className=\"text-sm font-medium\">Sort by</span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {SORT_OPTIONS.map(({ value, label }) => {\n const isSelected = sort === value;\n return (\n <button\n key={value}\n onClick={() => onSortChange(value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{label}</span>\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Trait sections — always expanded (pruning already removed the noise) */}\n {traitSections.length > 0 && (\n <>\n <div className=\"h-px w-full bg-border\" />\n {traitSections.map((section, i) => {\n const activeValues = selected[section.traitType] ?? [];\n return (\n <div key={section.traitType}>\n {i > 0 && <div className=\"h-px w-full bg-border mb-4\" />}\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium\">{section.traitType}</span>\n <span className=\"text-2xs text-muted-foreground\">\n {section.values.length} values\n </span>\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {section.values.map(({ value, count }) => {\n const isSelected = activeValues.includes(value);\n return (\n <button\n key={value}\n onClick={() => onToggleValue(section.traitType, value)}\n className={cn(PILL_BASE, isSelected ? PILL_ON : PILL_OFF)}\n >\n {isSelected && <Check className=\"h-3 w-3\" />}\n <span>{value}</span>\n <span className={isSelected ? \"opacity-80\" : \"opacity-60\"}>\n {count}\n </span>\n </button>\n );\n })}\n </div>\n </div>\n );\n })}\n </>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
@@ -56,7 +56,7 @@ function CommunityRewardsSection({
56
56
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "mt-1.5 text-sm text-muted-foreground leading-relaxed", children: "Earn XP. Share the Creator's Fund." })
57
57
  ] }),
58
58
  !isLoading && entries.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "border-y border-brand-orange/20 py-3", children: [
59
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Recently active" }),
59
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-2xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Recently active" }),
60
60
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex -space-x-2", children: entries.slice(0, 5).map((entry) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
61
61
  "span",
62
62
  {
@@ -99,7 +99,7 @@ function CommunityRewardsSection({
99
99
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-x-0 top-0 h-0.5 bg-brand-orange/40" }),
100
100
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-0.5 pt-1", children: [
101
101
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-2xl font-black tabular-nums leading-none", children: entry.totalXp.toLocaleString() }),
102
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "XP earned" })
102
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-2xs text-muted-foreground", children: "XP earned" })
103
103
  ] }),
104
104
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
105
105
  import_address_display.AddressDisplay,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/community-rewards-section.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight, Zap, Palette, ShoppingBag, MessageSquare } from \"lucide-react\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface CommunityRewardsEntry {\n address: string;\n totalXp: number;\n}\n\nexport interface CommunityRewardsSectionProps {\n entries: CommunityRewardsEntry[];\n isLoading?: boolean;\n rewardsHref: string;\n creatorHref: (address: string) => string;\n}\n\nconst EARN_ACTIONS = [\n { icon: Zap, label: \"Mint\" },\n { icon: Palette, label: \"Create\" },\n { icon: ShoppingBag, label: \"Trade\" },\n { icon: MessageSquare, label: \"Engage\" },\n];\n\nexport function CommunityRewardsSection({\n entries,\n isLoading = false,\n rewardsHref,\n creatorHref,\n}: CommunityRewardsSectionProps) {\n return (\n <section className=\"rounded-2xl bg-muted/50 dark:bg-card overflow-hidden grid lg:grid-cols-2 max-lg:divide-y lg:divide-x divide-brand-orange/20\">\n\n {/* Left — info panel */}\n <div className=\"px-7 py-8 sm:px-9 flex flex-col gap-6\">\n <div>\n {/* Creator's Fund eyebrow — shared with CreatorAirdropBanner */}\n <span className=\"text-xs font-bold uppercase tracking-widest text-brand-orange\">\n Creator&apos;s Fund\n </span>\n <h2 className=\"mt-4 text-2xl font-bold tracking-tight leading-tight\">\n Community Rewards\n </h2>\n <p className=\"mt-1.5 text-sm text-muted-foreground leading-relaxed\">\n Earn XP. Share the Creator&apos;s Fund.\n </p>\n </div>\n\n {/* Recently active — live rail sourced from the same entries as the\n member cards on the right, no rank/podium framing. */}\n {!isLoading && entries.length > 0 && (\n <div className=\"border-y border-brand-orange/20 py-3\">\n <p className=\"text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2\">\n Recently active\n </p>\n <div className=\"flex -space-x-2\">\n {entries.slice(0, 5).map((entry) => (\n <span\n key={entry.address}\n title={`${entry.totalXp.toLocaleString()} XP`}\n className=\"flex h-7 w-7 items-center justify-center rounded-full border-2 border-background bg-brand-orange/15 text-[9px] font-bold text-brand-orange\"\n >\n {entry.address.slice(2, 4).toUpperCase()}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Earn actions — plain text row */}\n <div className=\"flex flex-wrap gap-x-5 gap-y-2\">\n {EARN_ACTIONS.map(({ icon: Icon, label }) => (\n <span\n key={label}\n className=\"flex items-center gap-1.5 text-xs font-medium text-muted-foreground\"\n >\n <Icon className=\"h-3 w-3 text-brand-orange/70\" />\n {label}\n </span>\n ))}\n </div>\n\n <Link\n href={rewardsHref}\n className=\"mt-auto inline-flex items-center justify-center gap-2 rounded-xl bg-brand-orange px-6 py-3 text-sm font-semibold text-white transition-all hover:brightness-110 active:scale-[0.98]\"\n >\n Start earning XP\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </Link>\n </div>\n\n {/* Right — member cards */}\n <div className=\"p-5 sm:p-6\">\n {!isLoading && entries.length > 0 ? (\n <div className=\"grid grid-cols-2 gap-3 h-full\">\n {entries.slice(0, 4).map((entry) => (\n <Link\n key={entry.address}\n href={creatorHref(entry.address)}\n className=\"group relative rounded-xl bg-card dark:bg-muted/30 hover:bg-card/70 dark:hover:bg-muted/50 overflow-hidden transition-colors flex flex-col justify-between p-4\"\n >\n <div className=\"absolute inset-x-0 top-0 h-0.5 bg-brand-orange/40\" />\n\n {/* XP — hero number */}\n <div className=\"space-y-0.5 pt-1\">\n <p className=\"text-2xl font-black tabular-nums leading-none\">\n {entry.totalXp.toLocaleString()}\n </p>\n <p className=\"text-[11px] text-muted-foreground\">XP earned</p>\n </div>\n\n <AddressDisplay\n address={entry.address}\n chars={4}\n showCopy={false}\n className=\"text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors\"\n />\n </Link>\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 h-full\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"min-h-[100px] rounded-xl bg-card dark:bg-muted/30 animate-pulse\" />\n ))}\n </div>\n )}\n </div>\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCQ;AAlCR,kBAAiB;AACjB,0BAAqE;AACrE,6BAA+B;AAc/B,MAAM,eAAe;AAAA,EACnB,EAAE,MAAM,yBAAK,OAAO,OAAO;AAAA,EAC3B,EAAE,MAAM,6BAAS,OAAO,SAAS;AAAA,EACjC,EAAE,MAAM,iCAAa,OAAO,QAAQ;AAAA,EACpC,EAAE,MAAM,mCAAe,OAAO,SAAS;AACzC;AAEO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AACF,GAAiC;AAC/B,SACE,6CAAC,aAAQ,WAAU,+HAGjB;AAAA,iDAAC,SAAI,WAAU,yCACb;AAAA,mDAAC,SAEC;AAAA,oDAAC,UAAK,WAAU,iEAAgE,4BAEhF;AAAA,QACA,4CAAC,QAAG,WAAU,wDAAuD,+BAErE;AAAA,QACA,4CAAC,OAAE,WAAU,wDAAuD,gDAEpE;AAAA,SACF;AAAA,MAIC,CAAC,aAAa,QAAQ,SAAS,KAC9B,6CAAC,SAAI,WAAU,wCACb;AAAA,oDAAC,OAAE,WAAU,8EAA6E,6BAE1F;AAAA,QACA,4CAAC,SAAI,WAAU,mBACZ,kBAAQ,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UACxB;AAAA,UAAC;AAAA;AAAA,YAEC,OAAO,GAAG,MAAM,QAAQ,eAAe,CAAC;AAAA,YACxC,WAAU;AAAA,YAET,gBAAM,QAAQ,MAAM,GAAG,CAAC,EAAE,YAAY;AAAA;AAAA,UAJlC,MAAM;AAAA,QAKb,CACD,GACH;AAAA,SACF;AAAA,MAIF,4CAAC,SAAI,WAAU,kCACZ,uBAAa,IAAI,CAAC,EAAE,MAAM,MAAM,MAAM,MACrC;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,wDAAC,QAAK,WAAU,gCAA+B;AAAA,YAC9C;AAAA;AAAA;AAAA,QAJI;AAAA,MAKP,CACD,GACH;AAAA,MAEA;AAAA,QAAC,YAAAA;AAAA,QAAA;AAAA,UACC,MAAM;AAAA,UACN,WAAU;AAAA,UACX;AAAA;AAAA,YAEC,4CAAC,kCAAW,WAAU,eAAc;AAAA;AAAA;AAAA,MACtC;AAAA,OACF;AAAA,IAGA,4CAAC,SAAI,WAAU,cACZ,WAAC,aAAa,QAAQ,SAAS,IAC9B,4CAAC,SAAI,WAAU,iCACZ,kBAAQ,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UACxB;AAAA,MAAC,YAAAA;AAAA,MAAA;AAAA,QAEC,MAAM,YAAY,MAAM,OAAO;AAAA,QAC/B,WAAU;AAAA,QAEV;AAAA,sDAAC,SAAI,WAAU,qDAAoD;AAAA,UAGnE,6CAAC,SAAI,WAAU,oBACb;AAAA,wDAAC,OAAE,WAAU,iDACV,gBAAM,QAAQ,eAAe,GAChC;AAAA,YACA,4CAAC,OAAE,WAAU,qCAAoC,uBAAS;AAAA,aAC5D;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,MAAM;AAAA,cACf,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,MAnBK,MAAM;AAAA,IAoBb,CACD,GACH,IAEA,4CAAC,SAAI,WAAU,iCACZ,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,4CAAC,SAAY,WAAU,qEAAb,CAA+E,CAC1F,GACH,GAEJ;AAAA,KACF;AAEJ;","names":["Link"]}
1
+ {"version":3,"sources":["../../src/components/community-rewards-section.tsx"],"sourcesContent":["\"use client\";\n\nimport Link from \"next/link\";\nimport { ArrowRight, Zap, Palette, ShoppingBag, MessageSquare } from \"lucide-react\";\nimport { AddressDisplay } from \"./address-display.js\";\n\nexport interface CommunityRewardsEntry {\n address: string;\n totalXp: number;\n}\n\nexport interface CommunityRewardsSectionProps {\n entries: CommunityRewardsEntry[];\n isLoading?: boolean;\n rewardsHref: string;\n creatorHref: (address: string) => string;\n}\n\nconst EARN_ACTIONS = [\n { icon: Zap, label: \"Mint\" },\n { icon: Palette, label: \"Create\" },\n { icon: ShoppingBag, label: \"Trade\" },\n { icon: MessageSquare, label: \"Engage\" },\n];\n\nexport function CommunityRewardsSection({\n entries,\n isLoading = false,\n rewardsHref,\n creatorHref,\n}: CommunityRewardsSectionProps) {\n return (\n <section className=\"rounded-2xl bg-muted/50 dark:bg-card overflow-hidden grid lg:grid-cols-2 max-lg:divide-y lg:divide-x divide-brand-orange/20\">\n\n {/* Left — info panel */}\n <div className=\"px-7 py-8 sm:px-9 flex flex-col gap-6\">\n <div>\n {/* Creator's Fund eyebrow — shared with CreatorAirdropBanner */}\n <span className=\"text-xs font-bold uppercase tracking-widest text-brand-orange\">\n Creator&apos;s Fund\n </span>\n <h2 className=\"mt-4 text-2xl font-bold tracking-tight leading-tight\">\n Community Rewards\n </h2>\n <p className=\"mt-1.5 text-sm text-muted-foreground leading-relaxed\">\n Earn XP. Share the Creator&apos;s Fund.\n </p>\n </div>\n\n {/* Recently active — live rail sourced from the same entries as the\n member cards on the right, no rank/podium framing. */}\n {!isLoading && entries.length > 0 && (\n <div className=\"border-y border-brand-orange/20 py-3\">\n <p className=\"text-2xs font-bold uppercase tracking-widest text-muted-foreground mb-2\">\n Recently active\n </p>\n <div className=\"flex -space-x-2\">\n {entries.slice(0, 5).map((entry) => (\n <span\n key={entry.address}\n title={`${entry.totalXp.toLocaleString()} XP`}\n className=\"flex h-7 w-7 items-center justify-center rounded-full border-2 border-background bg-brand-orange/15 text-[9px] font-bold text-brand-orange\"\n >\n {entry.address.slice(2, 4).toUpperCase()}\n </span>\n ))}\n </div>\n </div>\n )}\n\n {/* Earn actions — plain text row */}\n <div className=\"flex flex-wrap gap-x-5 gap-y-2\">\n {EARN_ACTIONS.map(({ icon: Icon, label }) => (\n <span\n key={label}\n className=\"flex items-center gap-1.5 text-xs font-medium text-muted-foreground\"\n >\n <Icon className=\"h-3 w-3 text-brand-orange/70\" />\n {label}\n </span>\n ))}\n </div>\n\n <Link\n href={rewardsHref}\n className=\"mt-auto inline-flex items-center justify-center gap-2 rounded-xl bg-brand-orange px-6 py-3 text-sm font-semibold text-white transition-all hover:brightness-110 active:scale-[0.98]\"\n >\n Start earning XP\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </Link>\n </div>\n\n {/* Right — member cards */}\n <div className=\"p-5 sm:p-6\">\n {!isLoading && entries.length > 0 ? (\n <div className=\"grid grid-cols-2 gap-3 h-full\">\n {entries.slice(0, 4).map((entry) => (\n <Link\n key={entry.address}\n href={creatorHref(entry.address)}\n className=\"group relative rounded-xl bg-card dark:bg-muted/30 hover:bg-card/70 dark:hover:bg-muted/50 overflow-hidden transition-colors flex flex-col justify-between p-4\"\n >\n <div className=\"absolute inset-x-0 top-0 h-0.5 bg-brand-orange/40\" />\n\n {/* XP — hero number */}\n <div className=\"space-y-0.5 pt-1\">\n <p className=\"text-2xl font-black tabular-nums leading-none\">\n {entry.totalXp.toLocaleString()}\n </p>\n <p className=\"text-2xs text-muted-foreground\">XP earned</p>\n </div>\n\n <AddressDisplay\n address={entry.address}\n chars={4}\n showCopy={false}\n className=\"text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors\"\n />\n </Link>\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-2 gap-3 h-full\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"min-h-[100px] rounded-xl bg-card dark:bg-muted/30 animate-pulse\" />\n ))}\n </div>\n )}\n </div>\n </section>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCQ;AAlCR,kBAAiB;AACjB,0BAAqE;AACrE,6BAA+B;AAc/B,MAAM,eAAe;AAAA,EACnB,EAAE,MAAM,yBAAK,OAAO,OAAO;AAAA,EAC3B,EAAE,MAAM,6BAAS,OAAO,SAAS;AAAA,EACjC,EAAE,MAAM,iCAAa,OAAO,QAAQ;AAAA,EACpC,EAAE,MAAM,mCAAe,OAAO,SAAS;AACzC;AAEO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AACF,GAAiC;AAC/B,SACE,6CAAC,aAAQ,WAAU,+HAGjB;AAAA,iDAAC,SAAI,WAAU,yCACb;AAAA,mDAAC,SAEC;AAAA,oDAAC,UAAK,WAAU,iEAAgE,4BAEhF;AAAA,QACA,4CAAC,QAAG,WAAU,wDAAuD,+BAErE;AAAA,QACA,4CAAC,OAAE,WAAU,wDAAuD,gDAEpE;AAAA,SACF;AAAA,MAIC,CAAC,aAAa,QAAQ,SAAS,KAC9B,6CAAC,SAAI,WAAU,wCACb;AAAA,oDAAC,OAAE,WAAU,2EAA0E,6BAEvF;AAAA,QACA,4CAAC,SAAI,WAAU,mBACZ,kBAAQ,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UACxB;AAAA,UAAC;AAAA;AAAA,YAEC,OAAO,GAAG,MAAM,QAAQ,eAAe,CAAC;AAAA,YACxC,WAAU;AAAA,YAET,gBAAM,QAAQ,MAAM,GAAG,CAAC,EAAE,YAAY;AAAA;AAAA,UAJlC,MAAM;AAAA,QAKb,CACD,GACH;AAAA,SACF;AAAA,MAIF,4CAAC,SAAI,WAAU,kCACZ,uBAAa,IAAI,CAAC,EAAE,MAAM,MAAM,MAAM,MACrC;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,wDAAC,QAAK,WAAU,gCAA+B;AAAA,YAC9C;AAAA;AAAA;AAAA,QAJI;AAAA,MAKP,CACD,GACH;AAAA,MAEA;AAAA,QAAC,YAAAA;AAAA,QAAA;AAAA,UACC,MAAM;AAAA,UACN,WAAU;AAAA,UACX;AAAA;AAAA,YAEC,4CAAC,kCAAW,WAAU,eAAc;AAAA;AAAA;AAAA,MACtC;AAAA,OACF;AAAA,IAGA,4CAAC,SAAI,WAAU,cACZ,WAAC,aAAa,QAAQ,SAAS,IAC9B,4CAAC,SAAI,WAAU,iCACZ,kBAAQ,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,UACxB;AAAA,MAAC,YAAAA;AAAA,MAAA;AAAA,QAEC,MAAM,YAAY,MAAM,OAAO;AAAA,QAC/B,WAAU;AAAA,QAEV;AAAA,sDAAC,SAAI,WAAU,qDAAoD;AAAA,UAGnE,6CAAC,SAAI,WAAU,oBACb;AAAA,wDAAC,OAAE,WAAU,iDACV,gBAAM,QAAQ,eAAe,GAChC;AAAA,YACA,4CAAC,OAAE,WAAU,kCAAiC,uBAAS;AAAA,aACzD;AAAA,UAEA;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,MAAM;AAAA,cACf,OAAO;AAAA,cACP,UAAU;AAAA,cACV,WAAU;AAAA;AAAA,UACZ;AAAA;AAAA;AAAA,MAnBK,MAAM;AAAA,IAoBb,CACD,GACH,IAEA,4CAAC,SAAI,WAAU,iCACZ,gBAAM,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,MACjC,4CAAC,SAAY,WAAU,qEAAb,CAA+E,CAC1F,GACH,GAEJ;AAAA,KACF;AAEJ;","names":["Link"]}
@@ -23,7 +23,7 @@ function CommunityRewardsSection({
23
23
  /* @__PURE__ */ jsx("p", { className: "mt-1.5 text-sm text-muted-foreground leading-relaxed", children: "Earn XP. Share the Creator's Fund." })
24
24
  ] }),
25
25
  !isLoading && entries.length > 0 && /* @__PURE__ */ jsxs("div", { className: "border-y border-brand-orange/20 py-3", children: [
26
- /* @__PURE__ */ jsx("p", { className: "text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Recently active" }),
26
+ /* @__PURE__ */ jsx("p", { className: "text-2xs font-bold uppercase tracking-widest text-muted-foreground mb-2", children: "Recently active" }),
27
27
  /* @__PURE__ */ jsx("div", { className: "flex -space-x-2", children: entries.slice(0, 5).map((entry) => /* @__PURE__ */ jsx(
28
28
  "span",
29
29
  {
@@ -66,7 +66,7 @@ function CommunityRewardsSection({
66
66
  /* @__PURE__ */ jsx("div", { className: "absolute inset-x-0 top-0 h-0.5 bg-brand-orange/40" }),
67
67
  /* @__PURE__ */ jsxs("div", { className: "space-y-0.5 pt-1", children: [
68
68
  /* @__PURE__ */ jsx("p", { className: "text-2xl font-black tabular-nums leading-none", children: entry.totalXp.toLocaleString() }),
69
- /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "XP earned" })
69
+ /* @__PURE__ */ jsx("p", { className: "text-2xs text-muted-foreground", children: "XP earned" })
70
70
  ] }),
71
71
  /* @__PURE__ */ jsx(
72
72
  AddressDisplay,