@unicitylabs/sphere-ui 0.1.17 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -4
- package/dist/index.d.ts +101 -8
- package/dist/index.js +571 -58
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -140,15 +140,23 @@ Defined in `src/styles/tokens.css` via `@theme {}` block:
|
|
|
140
140
|
|
|
141
141
|
Backward-compatible aliases `admin-card`, `admin-input`, etc. are also available.
|
|
142
142
|
|
|
143
|
-
## Media components
|
|
143
|
+
## Media components
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
Components for project preview UIs — same look as sphere wallet's marketplace, so dev-portal and backoffice can show creators what their project will look like.
|
|
146
146
|
|
|
147
|
+
**Upload (v0.1.16+):**
|
|
147
148
|
- `<MediaUploader>` — drag-drop file upload + URL paste with progress, validation, error states. Pass an `uploadFn` prop that performs your presign → PUT → confirm flow against your backend.
|
|
148
149
|
- `<MediaGallery>` — sortable list of screenshots (max 10) via @dnd-kit; uses `<MediaUploader>` inline for adding items.
|
|
149
|
-
- `<MarketplaceProjectCard>` — live preview of how a project card will look in the sphere wallet marketplace. First marketplace-tier component in sphere-ui (rest are admin-tier).
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
**Marketplace preview (v0.1.17+):**
|
|
152
|
+
- `<MarketplaceProjectCard>` — 1:1 visual copy of sphere wallet's regular marketplace card. Banner + accentColor gradient, logo overflow ring, category badge, Users/Target/ThumbsUp stats, optional install button overlay. Framer-motion hover lift.
|
|
153
|
+
- `<FeaturedProjectCard>` — 1:1 copy of sphere wallet's "featured" variant. Full-banner background, Star badge, bottom-overlay content.
|
|
154
|
+
- `<InstalledProjectIcon>` — 1:1 copy of sphere wallet's desktop dock icon. Accepts `showLabel` prop for dock vs grid layouts.
|
|
155
|
+
- `<ProjectPagePreview>` — stateless version of sphere wallet's full `/apps/:slug` page. Hero + stats + social + screenshots strip + quests + achievements + reviews placeholder. Accepts all data via props.
|
|
156
|
+
|
|
157
|
+
All preview components are decorative — they don't fetch data, don't wrap navigation, don't trigger install. Use them in dev-portal/backoffice forms to show the creator what users will see.
|
|
158
|
+
|
|
159
|
+
Helper exports: `MEDIA_LIMITS`, `isMimeAllowed`, `isSizeAllowed`, `humanSize`. Types: `MediaKind`, `MediaMime`, `MediaLimit`, `MediaUploadFn`, `MediaUploadResult`, `MediaItem`, `QuestPreviewSummary`, `AchievementPreviewSummary`.
|
|
152
160
|
|
|
153
161
|
## Development
|
|
154
162
|
|
package/dist/index.d.ts
CHANGED
|
@@ -332,23 +332,116 @@ interface MarketplaceProjectCardProps {
|
|
|
332
332
|
tagline?: string;
|
|
333
333
|
logoUrl?: string | null;
|
|
334
334
|
bannerUrl?: string | null;
|
|
335
|
-
|
|
336
|
-
|
|
335
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
336
|
+
accentColor?: string;
|
|
337
|
+
category?: 'game' | 'defi' | 'social' | 'tool' | 'nft' | 'other' | string;
|
|
338
|
+
users?: number;
|
|
339
|
+
quests?: number;
|
|
340
|
+
positivePercent?: number;
|
|
341
|
+
ratingCount?: number;
|
|
342
|
+
/** Show install button overlay. Pass `'installed'` or `'available'` to render the right state. Pass `'none'` (default) to hide it (used in preview mode). */
|
|
343
|
+
installState?: 'none' | 'available' | 'installed';
|
|
344
|
+
/** Only fires when `installState !== 'none'`. */
|
|
345
|
+
onInstallClick?: () => void;
|
|
346
|
+
/** Optional click handler for the whole card (apps wrapping with router Link externally should leave this undefined). */
|
|
347
|
+
onClick?: () => void;
|
|
337
348
|
}
|
|
338
349
|
/**
|
|
339
|
-
* MarketplaceProjectCard —
|
|
350
|
+
* MarketplaceProjectCard — 1:1 visual copy of sphere wallet's ProjectCard.
|
|
340
351
|
*
|
|
341
352
|
* Used by dev-portal & backoffice as a live preview while editing a project,
|
|
342
|
-
* so authors can see
|
|
353
|
+
* so authors can see exactly how their card will look in the marketplace.
|
|
354
|
+
*
|
|
355
|
+
* No `<Link>` dependency — wrap externally if router navigation is needed.
|
|
356
|
+
*/
|
|
357
|
+
declare function MarketplaceProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, category, users, quests, positivePercent, ratingCount, installState, onInstallClick, onClick, }: MarketplaceProjectCardProps): react_jsx_runtime.JSX.Element;
|
|
358
|
+
|
|
359
|
+
interface FeaturedProjectCardProps {
|
|
360
|
+
name: string;
|
|
361
|
+
tagline?: string;
|
|
362
|
+
logoUrl?: string | null;
|
|
363
|
+
bannerUrl?: string | null;
|
|
364
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
365
|
+
accentColor?: string;
|
|
366
|
+
users?: number;
|
|
367
|
+
quests?: number;
|
|
368
|
+
positivePercent?: number;
|
|
369
|
+
ratingCount?: number;
|
|
370
|
+
onClick?: () => void;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* FeaturedProjectCard — 1:1 visual copy of sphere wallet's FeaturedProjectCard.
|
|
374
|
+
*
|
|
375
|
+
* Wide hero-style card used in marketplace featured rails.
|
|
376
|
+
* No `<Link>` dependency — wrap externally if router navigation is needed.
|
|
377
|
+
*/
|
|
378
|
+
declare function FeaturedProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, users, quests, positivePercent, ratingCount, onClick, }: FeaturedProjectCardProps): react_jsx_runtime.JSX.Element;
|
|
379
|
+
|
|
380
|
+
interface InstalledProjectIconProps {
|
|
381
|
+
name: string;
|
|
382
|
+
logoUrl?: string | null;
|
|
383
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
384
|
+
accentColor?: string;
|
|
385
|
+
onClick?: () => void;
|
|
386
|
+
/** When true, render the name label under the icon (dock vs grid layout). Default true. */
|
|
387
|
+
showLabel?: boolean;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* InstalledProjectIcon — 1:1 visual copy of sphere wallet's desktop installed-project icon.
|
|
391
|
+
*
|
|
392
|
+
* Stripped of context menu / install state / router navigation logic.
|
|
393
|
+
* Drop-in preview component for design tooling.
|
|
394
|
+
*/
|
|
395
|
+
declare function InstalledProjectIcon({ name, logoUrl, accentColor, onClick, showLabel, }: InstalledProjectIconProps): react_jsx_runtime.JSX.Element;
|
|
396
|
+
|
|
397
|
+
interface QuestPreviewSummary {
|
|
398
|
+
slug: string;
|
|
399
|
+
title: string;
|
|
400
|
+
description?: string;
|
|
401
|
+
points?: number;
|
|
402
|
+
difficulty?: 'easy' | 'medium' | 'hard';
|
|
403
|
+
}
|
|
404
|
+
interface AchievementPreviewSummary {
|
|
405
|
+
slug: string;
|
|
406
|
+
title: string;
|
|
407
|
+
imageUrl?: string | null;
|
|
408
|
+
points?: number;
|
|
409
|
+
}
|
|
410
|
+
interface ProjectPagePreviewProps {
|
|
411
|
+
name: string;
|
|
412
|
+
slug: string;
|
|
413
|
+
tagline?: string;
|
|
414
|
+
description?: string;
|
|
415
|
+
logoUrl?: string | null;
|
|
416
|
+
bannerUrl?: string | null;
|
|
417
|
+
accentColor?: string;
|
|
418
|
+
category?: string;
|
|
419
|
+
websiteUrl?: string;
|
|
420
|
+
discordUrl?: string;
|
|
421
|
+
twitterUrl?: string;
|
|
422
|
+
/** All media (screenshots + videos) for the strip */
|
|
423
|
+
media?: MediaItem[];
|
|
424
|
+
/** Live or static metrics — preview just shows what's passed */
|
|
425
|
+
users?: number;
|
|
426
|
+
activeQuests?: number;
|
|
427
|
+
positivePercent?: number;
|
|
428
|
+
ratingCount?: number;
|
|
429
|
+
/** Sample quests to show in the "Quests" section */
|
|
430
|
+
quests?: QuestPreviewSummary[];
|
|
431
|
+
/** Sample achievements */
|
|
432
|
+
achievements?: AchievementPreviewSummary[];
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* ProjectPagePreview — stateless 1:1 visual copy of sphere wallet's `/apps/:slug` page.
|
|
343
436
|
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
437
|
+
* Used by dev-portal & backoffice as a live preview while editing a project.
|
|
438
|
+
* No router / hooks / data fetching — every value comes via props.
|
|
346
439
|
*/
|
|
347
|
-
declare function
|
|
440
|
+
declare function ProjectPagePreview({ name, tagline, description, logoUrl, bannerUrl, accentColor, category, websiteUrl, discordUrl, twitterUrl, media, users, activeQuests, positivePercent, ratingCount, quests, achievements, }: ProjectPagePreviewProps): react_jsx_runtime.JSX.Element;
|
|
348
441
|
|
|
349
442
|
declare const MEDIA_LIMITS: Record<MediaKind, MediaLimit>;
|
|
350
443
|
declare function isMimeAllowed(kind: MediaKind, mime: string): mime is MediaMime;
|
|
351
444
|
declare function isSizeAllowed(kind: MediaKind, size: number): boolean;
|
|
352
445
|
declare function humanSize(bytes: number): string;
|
|
353
446
|
|
|
354
|
-
export { AddressDisplay, AlertBanner, AppLogo, Button, type ButtonProps, type ButtonVariant, ChainInput, ConfirmDialog, CustomSelect, DashboardLayout, DataTable, EmptyState, Field, FormModal, IconArrowRight, IconBack, IconChain, IconCheck, IconChevronDown, IconChevronUp, IconChevronsDown, IconChevronsRight, IconCircle, IconDiamond, IconEdit, IconPlay, IconPlus, IconQuests, IconSearch, IconSettings, IconStar, IconTracks, IconTrash, IconUndo, IconX, Input, type InputProps, JsonPanel, JsonToggleButton, MEDIA_LIMITS, MarketplaceProjectCard, type MarketplaceProjectCardProps, MediaGallery, type MediaGalleryProps, type MediaItem, type MediaKind, type MediaLimit, type MediaMime, type MediaUploadFn, type MediaUploadResult, MediaUploader, type MediaUploaderProps, type MemoCondition, MemoConditionsEditor, type NavGroup, type NavItem, PageShell, SearchInput, Section, Select, type SelectOption, type SelectProps, SidebarNav, Skeleton, SkeletonCircle, type SkeletonCircleProps, type SkeletonCircleSize, type SkeletonProps, SkeletonText, type SkeletonTextProps, StatusBadge, Textarea, type TextareaProps, humanSize, isMimeAllowed, isSizeAllowed, tagColor };
|
|
447
|
+
export { type AchievementPreviewSummary, AddressDisplay, AlertBanner, AppLogo, Button, type ButtonProps, type ButtonVariant, ChainInput, ConfirmDialog, CustomSelect, DashboardLayout, DataTable, EmptyState, FeaturedProjectCard, type FeaturedProjectCardProps, Field, FormModal, IconArrowRight, IconBack, IconChain, IconCheck, IconChevronDown, IconChevronUp, IconChevronsDown, IconChevronsRight, IconCircle, IconDiamond, IconEdit, IconPlay, IconPlus, IconQuests, IconSearch, IconSettings, IconStar, IconTracks, IconTrash, IconUndo, IconX, Input, type InputProps, InstalledProjectIcon, type InstalledProjectIconProps, JsonPanel, JsonToggleButton, MEDIA_LIMITS, MarketplaceProjectCard, type MarketplaceProjectCardProps, MediaGallery, type MediaGalleryProps, type MediaItem, type MediaKind, type MediaLimit, type MediaMime, type MediaUploadFn, type MediaUploadResult, MediaUploader, type MediaUploaderProps, type MemoCondition, MemoConditionsEditor, type NavGroup, type NavItem, PageShell, ProjectPagePreview, type ProjectPagePreviewProps, type QuestPreviewSummary, SearchInput, Section, Select, type SelectOption, type SelectProps, SidebarNav, Skeleton, SkeletonCircle, type SkeletonCircleProps, type SkeletonCircleSize, type SkeletonProps, SkeletonText, type SkeletonTextProps, StatusBadge, Textarea, type TextareaProps, humanSize, isMimeAllowed, isSizeAllowed, tagColor };
|
package/dist/index.js
CHANGED
|
@@ -1700,77 +1700,587 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1700
1700
|
}
|
|
1701
1701
|
|
|
1702
1702
|
// src/components/media/MarketplaceProjectCard.tsx
|
|
1703
|
-
import {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1703
|
+
import { motion } from "framer-motion";
|
|
1704
|
+
import { Users, Target, ThumbsUp, Plus, Check } from "lucide-react";
|
|
1705
|
+
import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1706
|
+
var categoryLabels = {
|
|
1707
|
+
game: "Game",
|
|
1708
|
+
defi: "DeFi",
|
|
1709
|
+
social: "Social",
|
|
1710
|
+
tool: "Tool",
|
|
1711
|
+
nft: "NFT",
|
|
1712
|
+
other: "Other"
|
|
1713
|
+
};
|
|
1712
1714
|
function MarketplaceProjectCard({
|
|
1713
1715
|
name,
|
|
1714
1716
|
tagline,
|
|
1715
1717
|
logoUrl,
|
|
1716
1718
|
bannerUrl,
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
+
accentColor = "#FF6F00",
|
|
1720
|
+
category,
|
|
1721
|
+
users = 0,
|
|
1722
|
+
quests = 0,
|
|
1723
|
+
positivePercent = 0,
|
|
1724
|
+
ratingCount = 0,
|
|
1725
|
+
installState = "none",
|
|
1726
|
+
onInstallClick,
|
|
1727
|
+
onClick
|
|
1719
1728
|
}) {
|
|
1720
1729
|
const hasBanner = !!bannerUrl;
|
|
1721
|
-
const
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
{
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1730
|
+
const installed = installState === "installed";
|
|
1731
|
+
const showInstall = installState !== "none";
|
|
1732
|
+
const handleInstall = (e) => {
|
|
1733
|
+
e.preventDefault();
|
|
1734
|
+
e.stopPropagation();
|
|
1735
|
+
onInstallClick?.();
|
|
1736
|
+
};
|
|
1737
|
+
const placeholderLogo = `https://placehold.co/44x44/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
1738
|
+
const card = /* @__PURE__ */ jsxs20(
|
|
1739
|
+
motion.div,
|
|
1740
|
+
{
|
|
1741
|
+
whileHover: { y: -4 },
|
|
1742
|
+
className: "no-text-shadow group rounded-2xl border border-neutral-200 dark:border-white/8 hover:border-orange-500/60 dark:hover:border-brand-orange/60 hover:shadow-lg hover:shadow-orange-500/10 dark:hover:shadow-brand-orange/15 transition-all duration-200 cursor-pointer relative overflow-hidden",
|
|
1743
|
+
children: [
|
|
1744
|
+
/* @__PURE__ */ jsxs20("div", { className: "relative h-24 overflow-hidden", "data-testid": "banner", children: [
|
|
1745
|
+
hasBanner ? /* @__PURE__ */ jsxs20(Fragment5, { children: [
|
|
1746
|
+
/* @__PURE__ */ jsx25(
|
|
1747
|
+
"div",
|
|
1748
|
+
{
|
|
1749
|
+
className: "absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105",
|
|
1750
|
+
style: { backgroundImage: `url(${bannerUrl})` }
|
|
1751
|
+
}
|
|
1752
|
+
),
|
|
1753
|
+
/* @__PURE__ */ jsx25("div", { className: "absolute inset-0", style: {
|
|
1754
|
+
background: `linear-gradient(to bottom, ${accentColor}33 0%, ${accentColor}99 100%)`
|
|
1755
|
+
} })
|
|
1756
|
+
] }) : /* @__PURE__ */ jsx25("div", { className: "absolute inset-0", style: {
|
|
1757
|
+
background: `linear-gradient(135deg, ${accentColor}cc 0%, ${accentColor}44 100%)`
|
|
1758
|
+
} }),
|
|
1759
|
+
showInstall && /* @__PURE__ */ jsx25(
|
|
1760
|
+
"button",
|
|
1761
|
+
{
|
|
1762
|
+
onClick: handleInstall,
|
|
1763
|
+
title: installed ? "Remove from Desktop" : "Add to Desktop",
|
|
1764
|
+
className: `absolute top-3 right-3 z-10 w-8 h-8 rounded-lg flex items-center justify-center backdrop-blur-sm transition-all ${installed ? "bg-green-500/30 text-white border border-green-400/40" : "bg-black/30 text-white/70 border border-white/15 hover:bg-orange-500/40 hover:text-white hover:border-orange-400/40"}`,
|
|
1765
|
+
children: installed ? /* @__PURE__ */ jsx25(Check, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx25(Plus, { className: "w-4 h-4" })
|
|
1766
|
+
}
|
|
1767
|
+
)
|
|
1768
|
+
] }),
|
|
1769
|
+
/* @__PURE__ */ jsxs20("div", { className: "p-4 bg-white dark:bg-white/4 dark:backdrop-blur-2xl", children: [
|
|
1770
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-start gap-3", children: [
|
|
1771
|
+
/* @__PURE__ */ jsx25(
|
|
1772
|
+
"img",
|
|
1773
|
+
{
|
|
1774
|
+
src: logoUrl ?? placeholderLogo,
|
|
1775
|
+
alt: name,
|
|
1776
|
+
className: "w-11 h-11 rounded-xl object-cover border border-neutral-200 dark:border-white/10 shrink-0 -mt-8 ring-2 ring-white dark:ring-[#0a0a0a] shadow-lg relative z-10",
|
|
1777
|
+
onError: (e) => {
|
|
1778
|
+
e.target.src = placeholderLogo;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
),
|
|
1782
|
+
/* @__PURE__ */ jsxs20("div", { className: "min-w-0 flex-1", children: [
|
|
1783
|
+
/* @__PURE__ */ jsx25("h3", { className: "font-semibold text-neutral-900 dark:text-white text-sm truncate", children: name }),
|
|
1784
|
+
/* @__PURE__ */ jsx25("p", { className: "text-neutral-500 dark:text-white/45 text-xs mt-0.5 h-8 line-clamp-2", children: tagline })
|
|
1785
|
+
] })
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between mt-3 pt-3 border-t border-neutral-100 dark:border-white/5", children: [
|
|
1788
|
+
category ? /* @__PURE__ */ jsx25("span", { className: "inline-flex px-2 py-0.5 rounded-md text-[10px] font-semibold uppercase tracking-wider border", style: {
|
|
1789
|
+
backgroundColor: `${accentColor}15`,
|
|
1790
|
+
color: accentColor,
|
|
1791
|
+
borderColor: `${accentColor}30`
|
|
1792
|
+
}, children: categoryLabels[category] ?? category }) : /* @__PURE__ */ jsx25("span", {}),
|
|
1793
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-3 text-[11px] text-neutral-400 dark:text-white/35", children: [
|
|
1794
|
+
/* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: "Users", children: [
|
|
1795
|
+
/* @__PURE__ */ jsx25(Users, { className: "w-3 h-3" }),
|
|
1796
|
+
users.toLocaleString()
|
|
1797
|
+
] }),
|
|
1798
|
+
/* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: "Active quests", children: [
|
|
1799
|
+
/* @__PURE__ */ jsx25(Target, { className: "w-3 h-3" }),
|
|
1800
|
+
quests.toLocaleString()
|
|
1801
|
+
] }),
|
|
1802
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: `${ratingCount} reviews`, children: [
|
|
1803
|
+
/* @__PURE__ */ jsx25(ThumbsUp, { className: "w-3 h-3" }),
|
|
1804
|
+
positivePercent,
|
|
1805
|
+
"%"
|
|
1806
|
+
] })
|
|
1807
|
+
] })
|
|
1808
|
+
] })
|
|
1809
|
+
] })
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
);
|
|
1813
|
+
if (onClick) {
|
|
1814
|
+
return /* @__PURE__ */ jsx25("button", { type: "button", onClick, className: "block w-full text-left", children: card });
|
|
1815
|
+
}
|
|
1816
|
+
return card;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
// src/components/media/FeaturedProjectCard.tsx
|
|
1820
|
+
import { motion as motion2 } from "framer-motion";
|
|
1821
|
+
import { Star, Users as Users2, Target as Target2, ThumbsUp as ThumbsUp2 } from "lucide-react";
|
|
1822
|
+
import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1823
|
+
function FeaturedProjectCard({
|
|
1824
|
+
name,
|
|
1825
|
+
tagline,
|
|
1826
|
+
logoUrl,
|
|
1827
|
+
bannerUrl,
|
|
1828
|
+
accentColor = "#FF6F00",
|
|
1829
|
+
users = 0,
|
|
1830
|
+
quests = 0,
|
|
1831
|
+
positivePercent = 0,
|
|
1832
|
+
ratingCount = 0,
|
|
1833
|
+
onClick
|
|
1834
|
+
}) {
|
|
1835
|
+
const placeholderLogo = `https://placehold.co/40x40/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
1836
|
+
const card = /* @__PURE__ */ jsxs21(
|
|
1837
|
+
motion2.div,
|
|
1838
|
+
{
|
|
1839
|
+
whileHover: { scale: 1.02, y: -2 },
|
|
1840
|
+
whileTap: { scale: 0.98 },
|
|
1841
|
+
className: "relative w-72 sm:w-80 h-44 rounded-2xl overflow-hidden shrink-0 cursor-pointer group",
|
|
1842
|
+
children: [
|
|
1843
|
+
/* @__PURE__ */ jsx26(
|
|
1749
1844
|
"div",
|
|
1750
1845
|
{
|
|
1751
|
-
className:
|
|
1752
|
-
|
|
1846
|
+
className: "absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105",
|
|
1847
|
+
"data-testid": "banner",
|
|
1848
|
+
style: {
|
|
1849
|
+
backgroundColor: accentColor,
|
|
1850
|
+
backgroundImage: bannerUrl ? `url(${bannerUrl})` : void 0
|
|
1851
|
+
}
|
|
1753
1852
|
}
|
|
1754
1853
|
),
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
/* @__PURE__ */ jsx25("span", { className: "text-[--accent]", children: "\u2605" }),
|
|
1763
|
-
" ",
|
|
1764
|
-
rating.toFixed(1)
|
|
1854
|
+
/* @__PURE__ */ jsx26("div", { className: "absolute inset-0", style: {
|
|
1855
|
+
background: bannerUrl ? `linear-gradient(135deg, ${accentColor}66 0%, transparent 60%)` : `linear-gradient(135deg, ${accentColor}cc 0%, ${accentColor}44 100%)`
|
|
1856
|
+
} }),
|
|
1857
|
+
/* @__PURE__ */ jsx26("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent" }),
|
|
1858
|
+
/* @__PURE__ */ jsxs21("div", { className: "absolute top-3 right-3 flex items-center gap-1 px-2 py-1 rounded-full bg-amber-500/90 text-white text-[10px] font-bold uppercase tracking-wider", children: [
|
|
1859
|
+
/* @__PURE__ */ jsx26(Star, { className: "w-3 h-3", fill: "currentColor" }),
|
|
1860
|
+
"Featured"
|
|
1765
1861
|
] }),
|
|
1766
|
-
|
|
1767
|
-
"
|
|
1768
|
-
|
|
1769
|
-
|
|
1862
|
+
/* @__PURE__ */ jsxs21("div", { className: "absolute bottom-0 left-0 right-0 p-4", children: [
|
|
1863
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-3", children: [
|
|
1864
|
+
/* @__PURE__ */ jsx26(
|
|
1865
|
+
"img",
|
|
1866
|
+
{
|
|
1867
|
+
src: logoUrl ?? placeholderLogo,
|
|
1868
|
+
alt: name,
|
|
1869
|
+
className: "w-10 h-10 rounded-xl object-cover border-2 border-white/20 shadow-lg",
|
|
1870
|
+
onError: (e) => {
|
|
1871
|
+
e.target.src = placeholderLogo;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
),
|
|
1875
|
+
/* @__PURE__ */ jsxs21("div", { className: "min-w-0", children: [
|
|
1876
|
+
/* @__PURE__ */ jsx26("h3", { className: "font-semibold text-white text-sm truncate", children: name }),
|
|
1877
|
+
/* @__PURE__ */ jsx26("p", { className: "text-white/70 text-xs truncate", children: tagline })
|
|
1878
|
+
] })
|
|
1879
|
+
] }),
|
|
1880
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-3 mt-2 text-[11px] text-white/60", children: [
|
|
1881
|
+
/* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: "Users", children: [
|
|
1882
|
+
/* @__PURE__ */ jsx26(Users2, { className: "w-3 h-3" }),
|
|
1883
|
+
users.toLocaleString()
|
|
1884
|
+
] }),
|
|
1885
|
+
/* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: "Active quests", children: [
|
|
1886
|
+
/* @__PURE__ */ jsx26(Target2, { className: "w-3 h-3" }),
|
|
1887
|
+
quests.toLocaleString()
|
|
1888
|
+
] }),
|
|
1889
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: `${ratingCount} reviews`, children: [
|
|
1890
|
+
/* @__PURE__ */ jsx26(ThumbsUp2, { className: "w-3 h-3" }),
|
|
1891
|
+
positivePercent,
|
|
1892
|
+
"%"
|
|
1893
|
+
] })
|
|
1894
|
+
] })
|
|
1770
1895
|
] })
|
|
1771
|
-
]
|
|
1772
|
-
|
|
1773
|
-
|
|
1896
|
+
]
|
|
1897
|
+
}
|
|
1898
|
+
);
|
|
1899
|
+
if (onClick) {
|
|
1900
|
+
return /* @__PURE__ */ jsx26("button", { type: "button", onClick, className: "block text-left", draggable: false, children: card });
|
|
1901
|
+
}
|
|
1902
|
+
return card;
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// src/components/media/InstalledProjectIcon.tsx
|
|
1906
|
+
import { useState as useState9 } from "react";
|
|
1907
|
+
import { motion as motion3 } from "framer-motion";
|
|
1908
|
+
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1909
|
+
function InstalledProjectIcon({
|
|
1910
|
+
name,
|
|
1911
|
+
logoUrl,
|
|
1912
|
+
accentColor = "#FF6F00",
|
|
1913
|
+
onClick,
|
|
1914
|
+
showLabel = true
|
|
1915
|
+
}) {
|
|
1916
|
+
const [imgError, setImgError] = useState9(false);
|
|
1917
|
+
return /* @__PURE__ */ jsx27("div", { className: "relative", children: /* @__PURE__ */ jsxs22(
|
|
1918
|
+
motion3.button,
|
|
1919
|
+
{
|
|
1920
|
+
type: "button",
|
|
1921
|
+
onClick,
|
|
1922
|
+
whileHover: { scale: 1.08, y: -4 },
|
|
1923
|
+
whileTap: { scale: 0.92 },
|
|
1924
|
+
transition: { duration: 0.05 },
|
|
1925
|
+
className: "flex flex-col items-center gap-2 p-3 rounded-2xl group cursor-pointer relative",
|
|
1926
|
+
children: [
|
|
1927
|
+
/* @__PURE__ */ jsxs22("div", { className: "relative", children: [
|
|
1928
|
+
/* @__PURE__ */ jsx27(
|
|
1929
|
+
"div",
|
|
1930
|
+
{
|
|
1931
|
+
className: "absolute -inset-1 blur-xl opacity-0 group-hover:opacity-50 transition-all duration-300 rounded-2xl",
|
|
1932
|
+
style: { backgroundColor: accentColor }
|
|
1933
|
+
}
|
|
1934
|
+
),
|
|
1935
|
+
/* @__PURE__ */ jsxs22(
|
|
1936
|
+
"div",
|
|
1937
|
+
{
|
|
1938
|
+
className: "relative w-14 h-14 sm:w-16 sm:h-16 rounded-2xl flex items-center justify-center shadow-lg group-hover:shadow-xl transition-all duration-200 overflow-hidden",
|
|
1939
|
+
style: { background: `linear-gradient(135deg, ${accentColor}, ${accentColor}cc)` },
|
|
1940
|
+
children: [
|
|
1941
|
+
/* @__PURE__ */ jsx27(
|
|
1942
|
+
"div",
|
|
1943
|
+
{
|
|
1944
|
+
className: "absolute inset-0 opacity-30 group-hover:opacity-50 transition-opacity duration-500",
|
|
1945
|
+
style: {
|
|
1946
|
+
backgroundImage: `radial-gradient(at 27% 37%, rgba(255,255,255,0.15) 0px, transparent 50%),
|
|
1947
|
+
radial-gradient(at 97% 21%, rgba(255,255,255,0.1) 0px, transparent 50%)`
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
),
|
|
1951
|
+
/* @__PURE__ */ jsx27("div", { className: "absolute top-0 right-0 w-8 h-8 bg-white/10 rounded-bl-full group-hover:w-10 group-hover:h-10 transition-all duration-300" }),
|
|
1952
|
+
logoUrl && !imgError ? /* @__PURE__ */ jsx27(
|
|
1953
|
+
"img",
|
|
1954
|
+
{
|
|
1955
|
+
src: logoUrl,
|
|
1956
|
+
alt: name,
|
|
1957
|
+
onError: () => setImgError(true),
|
|
1958
|
+
className: "w-9 h-9 sm:w-10 sm:h-10 object-contain rounded-lg relative z-10 drop-shadow-lg"
|
|
1959
|
+
}
|
|
1960
|
+
) : /* @__PURE__ */ jsx27("span", { className: "text-white font-bold text-lg relative z-10", children: name[0] ?? "?" })
|
|
1961
|
+
]
|
|
1962
|
+
}
|
|
1963
|
+
)
|
|
1964
|
+
] }),
|
|
1965
|
+
showLabel && /* @__PURE__ */ jsx27("span", { className: "text-xs sm:text-sm font-medium text-neutral-500 dark:text-[rgba(255,255,255,0.45)] group-hover:text-neutral-900 dark:group-hover:text-white transition-colors truncate max-w-20 sm:max-w-24 text-center leading-tight", children: name })
|
|
1966
|
+
]
|
|
1967
|
+
}
|
|
1968
|
+
) });
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// src/components/media/ProjectPagePreview.tsx
|
|
1972
|
+
import { motion as motion4 } from "framer-motion";
|
|
1973
|
+
import {
|
|
1974
|
+
ArrowLeft,
|
|
1975
|
+
ExternalLink,
|
|
1976
|
+
Users as Users3,
|
|
1977
|
+
Target as Target3,
|
|
1978
|
+
ThumbsUp as ThumbsUp3,
|
|
1979
|
+
Globe,
|
|
1980
|
+
Plus as Plus2,
|
|
1981
|
+
Star as Star2,
|
|
1982
|
+
Trophy,
|
|
1983
|
+
MessageCircle,
|
|
1984
|
+
Twitter,
|
|
1985
|
+
Zap
|
|
1986
|
+
} from "lucide-react";
|
|
1987
|
+
import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1988
|
+
var categoryLabels2 = {
|
|
1989
|
+
game: "Game",
|
|
1990
|
+
defi: "DeFi",
|
|
1991
|
+
social: "Social",
|
|
1992
|
+
tool: "Tool",
|
|
1993
|
+
nft: "NFT",
|
|
1994
|
+
other: "Other"
|
|
1995
|
+
};
|
|
1996
|
+
var difficultyStyles = {
|
|
1997
|
+
easy: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/25",
|
|
1998
|
+
medium: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
|
|
1999
|
+
hard: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
|
|
2000
|
+
};
|
|
2001
|
+
function getMediaThumb(m) {
|
|
2002
|
+
if (m.type !== "video") return m.url;
|
|
2003
|
+
const ytMatch = m.url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\s]+)/);
|
|
2004
|
+
return ytMatch ? `https://img.youtube.com/vi/${ytMatch[1]}/hqdefault.jpg` : m.url;
|
|
2005
|
+
}
|
|
2006
|
+
function ProjectPagePreview({
|
|
2007
|
+
name,
|
|
2008
|
+
tagline,
|
|
2009
|
+
description,
|
|
2010
|
+
logoUrl,
|
|
2011
|
+
bannerUrl,
|
|
2012
|
+
accentColor = "#FF6F00",
|
|
2013
|
+
category,
|
|
2014
|
+
websiteUrl,
|
|
2015
|
+
discordUrl,
|
|
2016
|
+
twitterUrl,
|
|
2017
|
+
media = [],
|
|
2018
|
+
users = 0,
|
|
2019
|
+
activeQuests = 0,
|
|
2020
|
+
positivePercent = 0,
|
|
2021
|
+
ratingCount = 0,
|
|
2022
|
+
quests = [],
|
|
2023
|
+
achievements = []
|
|
2024
|
+
}) {
|
|
2025
|
+
const placeholderLogo = `https://placehold.co/80x80/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
2026
|
+
return /* @__PURE__ */ jsxs23(
|
|
2027
|
+
motion4.div,
|
|
2028
|
+
{
|
|
2029
|
+
initial: { opacity: 0 },
|
|
2030
|
+
animate: { opacity: 1 },
|
|
2031
|
+
className: "text-neutral-900 dark:text-white pb-12",
|
|
2032
|
+
children: [
|
|
2033
|
+
/* @__PURE__ */ jsxs23("div", { className: "relative mx-4 sm:mx-6 mt-2", children: [
|
|
2034
|
+
/* @__PURE__ */ jsxs23("div", { className: "relative h-48 sm:h-64 rounded-2xl overflow-hidden", children: [
|
|
2035
|
+
/* @__PURE__ */ jsx28(
|
|
2036
|
+
"div",
|
|
2037
|
+
{
|
|
2038
|
+
className: "absolute inset-0 bg-cover bg-center",
|
|
2039
|
+
"data-testid": "banner",
|
|
2040
|
+
style: {
|
|
2041
|
+
backgroundColor: accentColor,
|
|
2042
|
+
backgroundImage: bannerUrl ? `url(${bannerUrl})` : void 0
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
),
|
|
2046
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" }),
|
|
2047
|
+
/* @__PURE__ */ jsxs23("div", { className: "absolute top-4 left-4 flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-black/40 backdrop-blur-sm text-white/80 text-sm", children: [
|
|
2048
|
+
/* @__PURE__ */ jsx28(ArrowLeft, { className: "w-4 h-4" }),
|
|
2049
|
+
"Explore"
|
|
2050
|
+
] })
|
|
2051
|
+
] }),
|
|
2052
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute -bottom-6 left-6 sm:left-8 z-10", children: /* @__PURE__ */ jsx28(
|
|
2053
|
+
"img",
|
|
2054
|
+
{
|
|
2055
|
+
src: logoUrl ?? placeholderLogo,
|
|
2056
|
+
alt: name,
|
|
2057
|
+
className: "w-16 h-16 sm:w-20 sm:h-20 rounded-2xl object-cover border-4 border-white dark:border-[#060606] shadow-xl",
|
|
2058
|
+
onError: (e) => {
|
|
2059
|
+
e.target.src = placeholderLogo;
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
) })
|
|
2063
|
+
] }),
|
|
2064
|
+
/* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pt-10 pb-6", children: /* @__PURE__ */ jsxs23("div", { className: "no-text-shadow max-w-5xl mx-auto bg-neutral-50 dark:bg-white/4 dark:backdrop-blur-2xl rounded-2xl border border-neutral-200 dark:border-white/8 p-6 sm:p-8", children: [
|
|
2065
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4", children: [
|
|
2066
|
+
/* @__PURE__ */ jsxs23("div", { children: [
|
|
2067
|
+
/* @__PURE__ */ jsx28("h1", { className: "text-2xl sm:text-3xl font-bold", children: name }),
|
|
2068
|
+
tagline && /* @__PURE__ */ jsx28("p", { className: "text-neutral-500 dark:text-white/55 mt-1", children: tagline }),
|
|
2069
|
+
category && /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-2 mt-3", children: /* @__PURE__ */ jsx28(
|
|
2070
|
+
"span",
|
|
2071
|
+
{
|
|
2072
|
+
className: "inline-flex px-2.5 py-0.5 rounded-lg text-xs font-semibold uppercase tracking-wider",
|
|
2073
|
+
style: {
|
|
2074
|
+
backgroundColor: `${accentColor}15`,
|
|
2075
|
+
color: accentColor,
|
|
2076
|
+
border: `1px solid ${accentColor}30`
|
|
2077
|
+
},
|
|
2078
|
+
children: categoryLabels2[category] ?? category
|
|
2079
|
+
}
|
|
2080
|
+
) })
|
|
2081
|
+
] }),
|
|
2082
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex gap-2 shrink-0 flex-wrap", children: [
|
|
2083
|
+
/* @__PURE__ */ jsxs23(
|
|
2084
|
+
"button",
|
|
2085
|
+
{
|
|
2086
|
+
type: "button",
|
|
2087
|
+
className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-xl font-semibold text-sm transition-all cursor-pointer bg-orange-500 dark:bg-brand-orange hover:bg-orange-600 dark:hover:bg-brand-orange-dark text-white shadow-lg shadow-orange-500/20",
|
|
2088
|
+
children: [
|
|
2089
|
+
/* @__PURE__ */ jsx28(Plus2, { className: "w-4 h-4" }),
|
|
2090
|
+
" Add to Desktop"
|
|
2091
|
+
]
|
|
2092
|
+
}
|
|
2093
|
+
),
|
|
2094
|
+
/* @__PURE__ */ jsxs23(
|
|
2095
|
+
"button",
|
|
2096
|
+
{
|
|
2097
|
+
type: "button",
|
|
2098
|
+
className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium border border-neutral-200 dark:border-white/8 text-neutral-600 dark:text-white/55 hover:text-neutral-900 dark:hover:text-white hover:border-neutral-300 dark:hover:border-white/15 transition-colors cursor-pointer",
|
|
2099
|
+
children: [
|
|
2100
|
+
"Open ",
|
|
2101
|
+
/* @__PURE__ */ jsx28(ExternalLink, { className: "w-3.5 h-3.5" })
|
|
2102
|
+
]
|
|
2103
|
+
}
|
|
2104
|
+
),
|
|
2105
|
+
websiteUrl && /* @__PURE__ */ jsxs23(
|
|
2106
|
+
"a",
|
|
2107
|
+
{
|
|
2108
|
+
href: websiteUrl,
|
|
2109
|
+
target: "_blank",
|
|
2110
|
+
rel: "noopener noreferrer",
|
|
2111
|
+
className: "inline-flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium border border-neutral-200 dark:border-white/8 text-neutral-600 dark:text-white/55 hover:text-neutral-900 dark:hover:text-white hover:border-neutral-300 dark:hover:border-white/15 transition-colors",
|
|
2112
|
+
children: [
|
|
2113
|
+
"Website ",
|
|
2114
|
+
/* @__PURE__ */ jsx28(Globe, { className: "w-3.5 h-3.5" })
|
|
2115
|
+
]
|
|
2116
|
+
}
|
|
2117
|
+
)
|
|
2118
|
+
] })
|
|
2119
|
+
] }),
|
|
2120
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex gap-6 sm:gap-10 border-t border-neutral-200 dark:border-white/8 mt-6 pt-5", children: [
|
|
2121
|
+
/* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2122
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2123
|
+
/* @__PURE__ */ jsx28(Users3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2124
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: users.toLocaleString() })
|
|
2125
|
+
] }),
|
|
2126
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: "Users" })
|
|
2127
|
+
] }),
|
|
2128
|
+
/* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2129
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2130
|
+
/* @__PURE__ */ jsx28(Target3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2131
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: activeQuests.toLocaleString() })
|
|
2132
|
+
] }),
|
|
2133
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: "Active Quests" })
|
|
2134
|
+
] }),
|
|
2135
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2136
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2137
|
+
/* @__PURE__ */ jsx28(ThumbsUp3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2138
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: [
|
|
2139
|
+
positivePercent,
|
|
2140
|
+
"%"
|
|
2141
|
+
] })
|
|
2142
|
+
] }),
|
|
2143
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: [
|
|
2144
|
+
ratingCount.toLocaleString(),
|
|
2145
|
+
" ",
|
|
2146
|
+
ratingCount === 1 ? "review" : "reviews"
|
|
2147
|
+
] })
|
|
2148
|
+
] })
|
|
2149
|
+
] })
|
|
2150
|
+
] }) }),
|
|
2151
|
+
media.length > 0 && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2152
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "Media" }),
|
|
2153
|
+
/* @__PURE__ */ jsx28("div", { className: "flex gap-3 overflow-x-auto scrollbar-hide pb-2", children: media.map((item, i) => {
|
|
2154
|
+
const isVideo = item.type === "video";
|
|
2155
|
+
return /* @__PURE__ */ jsxs23(
|
|
2156
|
+
"div",
|
|
2157
|
+
{
|
|
2158
|
+
className: "shrink-0 rounded-xl overflow-hidden border border-neutral-200 dark:border-white/8 relative",
|
|
2159
|
+
children: [
|
|
2160
|
+
/* @__PURE__ */ jsx28(
|
|
2161
|
+
"img",
|
|
2162
|
+
{
|
|
2163
|
+
src: getMediaThumb(item),
|
|
2164
|
+
alt: isVideo ? `Video ${i + 1}` : `Screenshot ${i + 1}`,
|
|
2165
|
+
draggable: false,
|
|
2166
|
+
className: "h-44 sm:h-52 w-72 sm:w-80 object-cover"
|
|
2167
|
+
}
|
|
2168
|
+
),
|
|
2169
|
+
isVideo && /* @__PURE__ */ jsx28("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none", children: /* @__PURE__ */ jsx28("div", { className: "w-14 h-14 rounded-full bg-black/60 backdrop-blur-sm flex items-center justify-center", children: /* @__PURE__ */ jsx28("svg", { className: "w-6 h-6 text-white ml-1", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx28("path", { d: "M8 5v14l11-7z" }) }) }) })
|
|
2170
|
+
]
|
|
2171
|
+
},
|
|
2172
|
+
i
|
|
2173
|
+
);
|
|
2174
|
+
}) })
|
|
2175
|
+
] }) }),
|
|
2176
|
+
description && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2177
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "About" }),
|
|
2178
|
+
/* @__PURE__ */ jsx28("div", { className: "no-text-shadow bg-neutral-50 dark:bg-white/4 dark:backdrop-blur-2xl rounded-2xl border border-neutral-200 dark:border-white/8 p-6", children: /* @__PURE__ */ jsx28("p", { className: "text-neutral-600 dark:text-white/55 text-sm leading-relaxed whitespace-pre-line", children: description }) })
|
|
2179
|
+
] }) }),
|
|
2180
|
+
quests.length > 0 && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2181
|
+
/* @__PURE__ */ jsxs23("h2", { className: "text-lg font-semibold mb-4", children: [
|
|
2182
|
+
"Quests ",
|
|
2183
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-neutral-400 dark:text-white/35 font-normal", children: [
|
|
2184
|
+
"(",
|
|
2185
|
+
quests.length,
|
|
2186
|
+
")"
|
|
2187
|
+
] })
|
|
2188
|
+
] }),
|
|
2189
|
+
/* @__PURE__ */ jsx28("div", { className: "grid sm:grid-cols-2 gap-3", children: quests.map((quest) => /* @__PURE__ */ jsxs23(
|
|
2190
|
+
"div",
|
|
2191
|
+
{
|
|
2192
|
+
className: "no-text-shadow bg-white dark:bg-white/4 dark:backdrop-blur-2xl rounded-xl border border-neutral-200 dark:border-white/8 p-4 relative overflow-hidden",
|
|
2193
|
+
children: [
|
|
2194
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute left-0 top-0 bottom-0 w-0.5", style: { backgroundColor: accentColor } }),
|
|
2195
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-start gap-3", children: [
|
|
2196
|
+
/* @__PURE__ */ jsxs23("div", { className: "min-w-0 flex-1", children: [
|
|
2197
|
+
/* @__PURE__ */ jsx28("h4", { className: "font-medium text-sm text-neutral-900 dark:text-white", children: quest.title }),
|
|
2198
|
+
quest.description && /* @__PURE__ */ jsx28("p", { className: "text-xs text-neutral-500 dark:text-white/40 mt-0.5 line-clamp-2", children: quest.description }),
|
|
2199
|
+
quest.difficulty && /* @__PURE__ */ jsx28("span", { className: `inline-flex mt-2 px-2 py-0.5 rounded-md text-[10px] font-semibold uppercase tracking-wider border ${difficultyStyles[quest.difficulty]}`, children: quest.difficulty })
|
|
2200
|
+
] }),
|
|
2201
|
+
typeof quest.points === "number" && /* @__PURE__ */ jsxs23("div", { className: "shrink-0 flex items-center gap-1 px-2 py-1 rounded-lg bg-orange-500/10 dark:bg-brand-orange-dim text-orange-600 dark:text-brand-orange text-xs font-semibold", children: [
|
|
2202
|
+
/* @__PURE__ */ jsx28(Zap, { className: "w-3 h-3" }),
|
|
2203
|
+
quest.points
|
|
2204
|
+
] })
|
|
2205
|
+
] })
|
|
2206
|
+
]
|
|
2207
|
+
},
|
|
2208
|
+
quest.slug
|
|
2209
|
+
)) })
|
|
2210
|
+
] }) }),
|
|
2211
|
+
achievements.length > 0 && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2212
|
+
/* @__PURE__ */ jsxs23("h2", { className: "text-lg font-semibold mb-4", children: [
|
|
2213
|
+
"Achievements ",
|
|
2214
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-neutral-400 dark:text-white/35 font-normal", children: [
|
|
2215
|
+
"(",
|
|
2216
|
+
achievements.length,
|
|
2217
|
+
")"
|
|
2218
|
+
] })
|
|
2219
|
+
] }),
|
|
2220
|
+
/* @__PURE__ */ jsx28("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: achievements.map((ach) => /* @__PURE__ */ jsxs23(
|
|
2221
|
+
"div",
|
|
2222
|
+
{
|
|
2223
|
+
className: "no-text-shadow bg-white dark:bg-white/4 dark:backdrop-blur-2xl rounded-xl border border-neutral-200 dark:border-white/8 p-4 flex flex-col items-center text-center",
|
|
2224
|
+
children: [
|
|
2225
|
+
/* @__PURE__ */ jsx28("div", { className: "w-16 h-16 rounded-xl overflow-hidden flex items-center justify-center mb-3", style: {
|
|
2226
|
+
backgroundColor: `${accentColor}15`,
|
|
2227
|
+
border: `1px solid ${accentColor}30`
|
|
2228
|
+
}, children: ach.imageUrl ? /* @__PURE__ */ jsx28("img", { src: ach.imageUrl, alt: ach.title, className: "w-full h-full object-cover" }) : /* @__PURE__ */ jsx28(Trophy, { className: "w-8 h-8", style: { color: accentColor } }) }),
|
|
2229
|
+
/* @__PURE__ */ jsx28("h4", { className: "text-sm font-medium text-neutral-900 dark:text-white line-clamp-2", children: ach.title }),
|
|
2230
|
+
typeof ach.points === "number" && /* @__PURE__ */ jsxs23("div", { className: "mt-2 flex items-center gap-1 text-xs font-semibold text-orange-600 dark:text-brand-orange", children: [
|
|
2231
|
+
/* @__PURE__ */ jsx28(Zap, { className: "w-3 h-3" }),
|
|
2232
|
+
ach.points
|
|
2233
|
+
] })
|
|
2234
|
+
]
|
|
2235
|
+
},
|
|
2236
|
+
ach.slug
|
|
2237
|
+
)) })
|
|
2238
|
+
] }) }),
|
|
2239
|
+
/* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2240
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "Reviews" }),
|
|
2241
|
+
/* @__PURE__ */ jsxs23("div", { className: "no-text-shadow bg-neutral-50 dark:bg-white/4 dark:backdrop-blur-2xl rounded-2xl border border-neutral-200 dark:border-white/8 p-6 flex flex-col items-center text-center", children: [
|
|
2242
|
+
/* @__PURE__ */ jsx28(Star2, { className: "w-8 h-8 text-neutral-300 dark:text-white/20 mb-2" }),
|
|
2243
|
+
/* @__PURE__ */ jsx28("p", { className: "text-sm text-neutral-500 dark:text-white/40", children: "Reviews coming soon" })
|
|
2244
|
+
] })
|
|
2245
|
+
] }) }),
|
|
2246
|
+
(websiteUrl || discordUrl || twitterUrl) && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto flex items-center gap-4", children: [
|
|
2247
|
+
websiteUrl && /* @__PURE__ */ jsx28(
|
|
2248
|
+
"a",
|
|
2249
|
+
{
|
|
2250
|
+
href: websiteUrl,
|
|
2251
|
+
target: "_blank",
|
|
2252
|
+
rel: "noopener noreferrer",
|
|
2253
|
+
"aria-label": "Website",
|
|
2254
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2255
|
+
children: /* @__PURE__ */ jsx28(Globe, { className: "w-5 h-5" })
|
|
2256
|
+
}
|
|
2257
|
+
),
|
|
2258
|
+
twitterUrl && /* @__PURE__ */ jsx28(
|
|
2259
|
+
"a",
|
|
2260
|
+
{
|
|
2261
|
+
href: twitterUrl,
|
|
2262
|
+
target: "_blank",
|
|
2263
|
+
rel: "noopener noreferrer",
|
|
2264
|
+
"aria-label": "Twitter / X",
|
|
2265
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2266
|
+
children: /* @__PURE__ */ jsx28(Twitter, { className: "w-5 h-5" })
|
|
2267
|
+
}
|
|
2268
|
+
),
|
|
2269
|
+
discordUrl && /* @__PURE__ */ jsx28(
|
|
2270
|
+
"a",
|
|
2271
|
+
{
|
|
2272
|
+
href: discordUrl,
|
|
2273
|
+
target: "_blank",
|
|
2274
|
+
rel: "noopener noreferrer",
|
|
2275
|
+
"aria-label": "Discord",
|
|
2276
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2277
|
+
children: /* @__PURE__ */ jsx28(MessageCircle, { className: "w-5 h-5" })
|
|
2278
|
+
}
|
|
2279
|
+
)
|
|
2280
|
+
] }) })
|
|
2281
|
+
]
|
|
2282
|
+
}
|
|
2283
|
+
);
|
|
1774
2284
|
}
|
|
1775
2285
|
export {
|
|
1776
2286
|
AddressDisplay,
|
|
@@ -1783,6 +2293,7 @@ export {
|
|
|
1783
2293
|
DashboardLayout,
|
|
1784
2294
|
DataTable,
|
|
1785
2295
|
EmptyState,
|
|
2296
|
+
FeaturedProjectCard,
|
|
1786
2297
|
Field,
|
|
1787
2298
|
FormModal,
|
|
1788
2299
|
IconArrowRight,
|
|
@@ -1807,6 +2318,7 @@ export {
|
|
|
1807
2318
|
IconUndo,
|
|
1808
2319
|
IconX,
|
|
1809
2320
|
Input,
|
|
2321
|
+
InstalledProjectIcon,
|
|
1810
2322
|
JsonPanel,
|
|
1811
2323
|
JsonToggleButton,
|
|
1812
2324
|
MEDIA_LIMITS,
|
|
@@ -1815,6 +2327,7 @@ export {
|
|
|
1815
2327
|
MediaUploader,
|
|
1816
2328
|
MemoConditionsEditor,
|
|
1817
2329
|
PageShell,
|
|
2330
|
+
ProjectPagePreview,
|
|
1818
2331
|
SearchInput,
|
|
1819
2332
|
Section,
|
|
1820
2333
|
Select,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unicitylabs/sphere-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@dnd-kit/sortable": "^8.0.0 || ^10.0.0",
|
|
49
49
|
"@tanstack/react-query": "^5.0.0",
|
|
50
50
|
"@tanstack/react-table": "^8.0.0",
|
|
51
|
+
"framer-motion": "^11.18.2",
|
|
51
52
|
"lucide-react": ">=0.400.0",
|
|
52
53
|
"react": "^19.0.0",
|
|
53
54
|
"react-dom": "^19.0.0",
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
"@types/react": "^19.0.0",
|
|
72
73
|
"@types/react-dom": "^19.0.0",
|
|
73
74
|
"@vitejs/plugin-react": "^4.7.0",
|
|
75
|
+
"framer-motion": "^11.18.2",
|
|
74
76
|
"jsdom": "^25.0.1",
|
|
75
77
|
"lucide-react": "^0.400.0",
|
|
76
78
|
"react": "^19.0.0",
|