@unicitylabs/sphere-ui 0.1.17 → 0.1.19
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 +116 -10
- package/dist/index.js +680 -96
- 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
|
@@ -310,8 +310,19 @@ interface MediaUploaderProps {
|
|
|
310
310
|
onChange: (url: string | null) => void;
|
|
311
311
|
uploadFn: MediaUploadFn;
|
|
312
312
|
label?: string;
|
|
313
|
-
|
|
314
|
-
|
|
313
|
+
/**
|
|
314
|
+
* When true, do NOT call uploadFn on file selection. Instead store the file
|
|
315
|
+
* locally, render a blob-URL preview, and call onFileSelected(file). The
|
|
316
|
+
* consumer is responsible for uploading later (e.g. after creating the
|
|
317
|
+
* parent entity to get a real ownerId).
|
|
318
|
+
*
|
|
319
|
+
* In this mode the URL paste fallback is hidden — the parent entity does
|
|
320
|
+
* not yet exist, so an external URL can't be persisted to it anyway.
|
|
321
|
+
*/
|
|
322
|
+
deferUpload?: boolean;
|
|
323
|
+
onFileSelected?: (file: File | null) => void;
|
|
324
|
+
}
|
|
325
|
+
declare function MediaUploader({ kind, ownerType, ownerId, value, onChange, uploadFn, label, deferUpload, onFileSelected, }: MediaUploaderProps): react_jsx_runtime.JSX.Element;
|
|
315
326
|
|
|
316
327
|
interface MediaItem {
|
|
317
328
|
type: 'screenshot' | 'video';
|
|
@@ -332,23 +343,118 @@ interface MarketplaceProjectCardProps {
|
|
|
332
343
|
tagline?: string;
|
|
333
344
|
logoUrl?: string | null;
|
|
334
345
|
bannerUrl?: string | null;
|
|
335
|
-
|
|
336
|
-
|
|
346
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
347
|
+
accentColor?: string;
|
|
348
|
+
category?: 'game' | 'defi' | 'social' | 'tool' | 'nft' | 'other' | string;
|
|
349
|
+
users?: number;
|
|
350
|
+
quests?: number;
|
|
351
|
+
positivePercent?: number;
|
|
352
|
+
ratingCount?: number;
|
|
353
|
+
/** Show install button overlay. Pass `'installed'` or `'available'` to render the right state. Pass `'none'` (default) to hide it (used in preview mode). */
|
|
354
|
+
installState?: 'none' | 'available' | 'installed';
|
|
355
|
+
/** Only fires when `installState !== 'none'`. */
|
|
356
|
+
onInstallClick?: () => void;
|
|
357
|
+
/** Optional click handler for the whole card (apps wrapping with router Link externally should leave this undefined). */
|
|
358
|
+
onClick?: () => void;
|
|
337
359
|
}
|
|
338
360
|
/**
|
|
339
|
-
* MarketplaceProjectCard —
|
|
361
|
+
* MarketplaceProjectCard — 1:1 visual copy of sphere wallet's ProjectCard.
|
|
340
362
|
*
|
|
341
363
|
* Used by dev-portal & backoffice as a live preview while editing a project,
|
|
342
|
-
* so authors can see
|
|
364
|
+
* so authors can see exactly how their card will look in the marketplace.
|
|
365
|
+
*
|
|
366
|
+
* No `<Link>` dependency — wrap externally if router navigation is needed.
|
|
367
|
+
*/
|
|
368
|
+
declare function MarketplaceProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, category, users, quests, positivePercent, ratingCount, installState, onInstallClick, onClick, }: MarketplaceProjectCardProps): react_jsx_runtime.JSX.Element;
|
|
369
|
+
|
|
370
|
+
interface FeaturedProjectCardProps {
|
|
371
|
+
name: string;
|
|
372
|
+
tagline?: string;
|
|
373
|
+
logoUrl?: string | null;
|
|
374
|
+
bannerUrl?: string | null;
|
|
375
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
376
|
+
accentColor?: string;
|
|
377
|
+
users?: number;
|
|
378
|
+
quests?: number;
|
|
379
|
+
positivePercent?: number;
|
|
380
|
+
ratingCount?: number;
|
|
381
|
+
onClick?: () => void;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* FeaturedProjectCard — 1:1 visual copy of sphere wallet's FeaturedProjectCard.
|
|
385
|
+
*
|
|
386
|
+
* Wide hero-style card used in marketplace featured rails.
|
|
387
|
+
* No `<Link>` dependency — wrap externally if router navigation is needed.
|
|
388
|
+
*/
|
|
389
|
+
declare function FeaturedProjectCard({ name, tagline, logoUrl, bannerUrl, accentColor, users, quests, positivePercent, ratingCount, onClick, }: FeaturedProjectCardProps): react_jsx_runtime.JSX.Element;
|
|
390
|
+
|
|
391
|
+
interface InstalledProjectIconProps {
|
|
392
|
+
name: string;
|
|
393
|
+
logoUrl?: string | null;
|
|
394
|
+
/** Hex like "#FF6F00". Defaults to brand orange. */
|
|
395
|
+
accentColor?: string;
|
|
396
|
+
onClick?: () => void;
|
|
397
|
+
/** When true, render the name label under the icon (dock vs grid layout). Default true. */
|
|
398
|
+
showLabel?: boolean;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* InstalledProjectIcon — 1:1 visual copy of sphere wallet's desktop installed-project icon.
|
|
402
|
+
*
|
|
403
|
+
* Stripped of context menu / install state / router navigation logic.
|
|
404
|
+
* Drop-in preview component for design tooling.
|
|
405
|
+
*/
|
|
406
|
+
declare function InstalledProjectIcon({ name, logoUrl, accentColor, onClick, showLabel, }: InstalledProjectIconProps): react_jsx_runtime.JSX.Element;
|
|
407
|
+
|
|
408
|
+
interface QuestPreviewSummary {
|
|
409
|
+
slug: string;
|
|
410
|
+
title: string;
|
|
411
|
+
description?: string;
|
|
412
|
+
points?: number;
|
|
413
|
+
difficulty?: 'easy' | 'medium' | 'hard';
|
|
414
|
+
}
|
|
415
|
+
interface AchievementPreviewSummary {
|
|
416
|
+
slug: string;
|
|
417
|
+
title: string;
|
|
418
|
+
imageUrl?: string | null;
|
|
419
|
+
points?: number;
|
|
420
|
+
}
|
|
421
|
+
interface ProjectPagePreviewProps {
|
|
422
|
+
name: string;
|
|
423
|
+
slug: string;
|
|
424
|
+
tagline?: string;
|
|
425
|
+
description?: string;
|
|
426
|
+
logoUrl?: string | null;
|
|
427
|
+
bannerUrl?: string | null;
|
|
428
|
+
accentColor?: string;
|
|
429
|
+
category?: string;
|
|
430
|
+
websiteUrl?: string;
|
|
431
|
+
discordUrl?: string;
|
|
432
|
+
twitterUrl?: string;
|
|
433
|
+
/** All media (screenshots + videos) for the strip */
|
|
434
|
+
media?: MediaItem[];
|
|
435
|
+
/** Live or static metrics — preview just shows what's passed */
|
|
436
|
+
users?: number;
|
|
437
|
+
activeQuests?: number;
|
|
438
|
+
positivePercent?: number;
|
|
439
|
+
ratingCount?: number;
|
|
440
|
+
/** Sample quests to show in the "Quests" section */
|
|
441
|
+
quests?: QuestPreviewSummary[];
|
|
442
|
+
/** Sample achievements */
|
|
443
|
+
achievements?: AchievementPreviewSummary[];
|
|
444
|
+
/** Project tags — first 3 are rendered as chips next to the category badge */
|
|
445
|
+
tags?: string[];
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* ProjectPagePreview — stateless 1:1 visual copy of sphere wallet's `/apps/:slug` page.
|
|
343
449
|
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
450
|
+
* Used by dev-portal & backoffice as a live preview while editing a project.
|
|
451
|
+
* No router / hooks / data fetching — every value comes via props.
|
|
346
452
|
*/
|
|
347
|
-
declare function
|
|
453
|
+
declare function ProjectPagePreview({ name, tagline, description, logoUrl, bannerUrl, accentColor, category, websiteUrl, discordUrl, twitterUrl, media, users, activeQuests, positivePercent, ratingCount, quests, achievements, tags, }: ProjectPagePreviewProps): react_jsx_runtime.JSX.Element;
|
|
348
454
|
|
|
349
455
|
declare const MEDIA_LIMITS: Record<MediaKind, MediaLimit>;
|
|
350
456
|
declare function isMimeAllowed(kind: MediaKind, mime: string): mime is MediaMime;
|
|
351
457
|
declare function isSizeAllowed(kind: MediaKind, size: number): boolean;
|
|
352
458
|
declare function humanSize(bytes: number): string;
|
|
353
459
|
|
|
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 };
|
|
460
|
+
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
|
@@ -1418,12 +1418,16 @@ function MediaUploader({
|
|
|
1418
1418
|
value,
|
|
1419
1419
|
onChange,
|
|
1420
1420
|
uploadFn,
|
|
1421
|
-
label
|
|
1421
|
+
label,
|
|
1422
|
+
deferUpload,
|
|
1423
|
+
onFileSelected
|
|
1422
1424
|
}) {
|
|
1423
1425
|
const limit = MEDIA_LIMITS[kind];
|
|
1424
1426
|
const [state, setState] = useState7({ phase: "idle" });
|
|
1425
1427
|
const [urlInput, setUrlInput] = useState7(value && !value.startsWith("blob:") ? value : "");
|
|
1426
1428
|
const previewRef = useRef3(null);
|
|
1429
|
+
const fileInputRef = useRef3(null);
|
|
1430
|
+
const isPlaceholder = value?.includes("placehold.co") ?? false;
|
|
1427
1431
|
useEffect5(
|
|
1428
1432
|
() => () => {
|
|
1429
1433
|
if (previewRef.current) {
|
|
@@ -1449,6 +1453,14 @@ function MediaUploader({
|
|
|
1449
1453
|
});
|
|
1450
1454
|
return;
|
|
1451
1455
|
}
|
|
1456
|
+
if (deferUpload) {
|
|
1457
|
+
if (previewRef.current) URL.revokeObjectURL(previewRef.current);
|
|
1458
|
+
previewRef.current = URL.createObjectURL(file);
|
|
1459
|
+
setState({ phase: "pending", file });
|
|
1460
|
+
onFileSelected?.(file);
|
|
1461
|
+
onChange(null);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1452
1464
|
const abort = new AbortController();
|
|
1453
1465
|
setState({ phase: "uploading", file, progress: 0, abort });
|
|
1454
1466
|
if (previewRef.current) URL.revokeObjectURL(previewRef.current);
|
|
@@ -1472,7 +1484,7 @@ function MediaUploader({
|
|
|
1472
1484
|
setState({ phase: "error", message });
|
|
1473
1485
|
}
|
|
1474
1486
|
},
|
|
1475
|
-
[kind, ownerType, ownerId, uploadFn, onChange, limit]
|
|
1487
|
+
[kind, ownerType, ownerId, uploadFn, onChange, limit, deferUpload, onFileSelected]
|
|
1476
1488
|
);
|
|
1477
1489
|
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
1478
1490
|
accept: Object.fromEntries(limit.mimes.map((m) => [m, []])),
|
|
@@ -1498,31 +1510,88 @@ function MediaUploader({
|
|
|
1498
1510
|
}
|
|
1499
1511
|
}
|
|
1500
1512
|
});
|
|
1513
|
+
const hasUploadedValue = !!value && !isPlaceholder && !value.startsWith("blob:");
|
|
1514
|
+
const hasPendingFile = state.phase === "pending" && !!previewRef.current;
|
|
1515
|
+
const hasDoneUpload = state.phase === "done" && hasUploadedValue;
|
|
1516
|
+
const hasImage = hasPendingFile || hasDoneUpload || state.phase === "idle" && hasUploadedValue;
|
|
1517
|
+
const thumbnailSrc = hasPendingFile ? previewRef.current : hasUploadedValue ? value : "";
|
|
1518
|
+
const isSelected = hasPendingFile;
|
|
1519
|
+
const handleReplace = (e) => {
|
|
1520
|
+
e.stopPropagation();
|
|
1521
|
+
fileInputRef.current?.querySelector("input")?.click();
|
|
1522
|
+
};
|
|
1523
|
+
const handleRemove = (e) => {
|
|
1524
|
+
e.stopPropagation();
|
|
1525
|
+
if (previewRef.current) {
|
|
1526
|
+
URL.revokeObjectURL(previewRef.current);
|
|
1527
|
+
previewRef.current = null;
|
|
1528
|
+
}
|
|
1529
|
+
setState({ phase: "idle" });
|
|
1530
|
+
onChange(null);
|
|
1531
|
+
setUrlInput("");
|
|
1532
|
+
if (deferUpload) onFileSelected?.(null);
|
|
1533
|
+
};
|
|
1501
1534
|
return /* @__PURE__ */ jsxs18("div", { className: "space-y-2", children: [
|
|
1502
|
-
label && /* @__PURE__ */ jsx23("div", { className: "text-sm text-
|
|
1535
|
+
label && /* @__PURE__ */ jsx23("div", { className: "text-sm text-neutral-700 dark:text-white/70", children: label }),
|
|
1503
1536
|
/* @__PURE__ */ jsxs18(
|
|
1504
1537
|
"div",
|
|
1505
1538
|
{
|
|
1506
1539
|
...getRootProps(),
|
|
1507
|
-
className: `border-2 border-dashed rounded-
|
|
1540
|
+
className: `border-2 border-dashed rounded-lg p-6 text-center cursor-pointer transition-colors ${isDragActive ? "border-orange-500 dark:border-brand-orange bg-orange-500/10 dark:bg-brand-orange/15" : "border-neutral-200 dark:border-white/8"}`,
|
|
1508
1541
|
children: [
|
|
1509
|
-
/* @__PURE__ */ jsx23("input", { ...getInputProps(), "aria-label": "file uploader" }),
|
|
1510
|
-
|
|
1542
|
+
/* @__PURE__ */ jsx23("span", { ref: fileInputRef, style: { display: "contents" }, children: /* @__PURE__ */ jsx23("input", { ...getInputProps(), "aria-label": "file uploader" }) }),
|
|
1543
|
+
hasImage ? /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-4", children: [
|
|
1544
|
+
/* @__PURE__ */ jsx23(
|
|
1545
|
+
"img",
|
|
1546
|
+
{
|
|
1547
|
+
src: thumbnailSrc,
|
|
1548
|
+
alt: "uploaded",
|
|
1549
|
+
className: "w-16 h-16 rounded-lg object-cover border border-neutral-200 dark:border-white/8 shrink-0"
|
|
1550
|
+
}
|
|
1551
|
+
),
|
|
1552
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex-1 text-left min-w-0", children: [
|
|
1553
|
+
/* @__PURE__ */ jsxs18("div", { className: "text-sm text-green-500 dark:text-green-400 flex items-center gap-1", children: [
|
|
1554
|
+
/* @__PURE__ */ jsx23("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx23("polyline", { points: "20 6 9 17 4 12" }) }),
|
|
1555
|
+
isSelected ? "Selected" : "Uploaded"
|
|
1556
|
+
] }),
|
|
1557
|
+
isSelected && /* @__PURE__ */ jsx23("div", { className: "text-[10px] text-neutral-500 dark:text-white/45 mt-0.5", children: "Uploads when you save" }),
|
|
1558
|
+
hasPendingFile && /* @__PURE__ */ jsx23("div", { className: "text-xs text-neutral-500 dark:text-white/45 mt-0.5 truncate", children: state.file.name }),
|
|
1559
|
+
/* @__PURE__ */ jsxs18("div", { className: "flex gap-2 mt-2 text-xs", children: [
|
|
1560
|
+
/* @__PURE__ */ jsx23(
|
|
1561
|
+
"button",
|
|
1562
|
+
{
|
|
1563
|
+
type: "button",
|
|
1564
|
+
onClick: handleReplace,
|
|
1565
|
+
className: "text-neutral-700 dark:text-white/70 hover:text-orange-500 dark:hover:text-brand-orange underline-offset-2 hover:underline",
|
|
1566
|
+
children: "Replace"
|
|
1567
|
+
}
|
|
1568
|
+
),
|
|
1569
|
+
/* @__PURE__ */ jsx23(
|
|
1570
|
+
"button",
|
|
1571
|
+
{
|
|
1572
|
+
type: "button",
|
|
1573
|
+
onClick: handleRemove,
|
|
1574
|
+
className: "text-neutral-700 dark:text-white/70 hover:text-red-500 dark:hover:text-red-400 underline-offset-2 hover:underline",
|
|
1575
|
+
children: "Remove"
|
|
1576
|
+
}
|
|
1577
|
+
)
|
|
1578
|
+
] })
|
|
1579
|
+
] })
|
|
1580
|
+
] }) : state.phase === "idle" ? /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1511
1581
|
/* @__PURE__ */ jsx23("div", { className: "text-sm mb-1", children: "Drop image here or click to choose" }),
|
|
1512
|
-
/* @__PURE__ */ jsxs18("div", { className: "text-xs text-
|
|
1582
|
+
/* @__PURE__ */ jsxs18("div", { className: "text-xs text-neutral-500 dark:text-white/45", children: [
|
|
1513
1583
|
formatExtensions(limit.mimes),
|
|
1514
1584
|
" \xB7 max ",
|
|
1515
1585
|
humanSize(limit.maxSize),
|
|
1516
1586
|
limit.maxWidth && limit.maxHeight && ` \xB7 ${limit.maxWidth}\xD7${limit.maxHeight}`
|
|
1517
1587
|
] })
|
|
1518
|
-
] }),
|
|
1519
|
-
state.phase === "uploading" && /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1588
|
+
] }) : state.phase === "uploading" ? /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1520
1589
|
previewRef.current && /* @__PURE__ */ jsx23(
|
|
1521
1590
|
"img",
|
|
1522
1591
|
{
|
|
1523
1592
|
src: previewRef.current,
|
|
1524
1593
|
alt: "upload preview",
|
|
1525
|
-
className: "max-w-[64px] max-h-[64px] rounded
|
|
1594
|
+
className: "max-w-[64px] max-h-[64px] rounded object-cover border border-neutral-200 dark:border-white/8 mx-auto mb-2"
|
|
1526
1595
|
}
|
|
1527
1596
|
),
|
|
1528
1597
|
/* @__PURE__ */ jsxs18("div", { className: "text-sm", children: [
|
|
@@ -1552,9 +1621,7 @@ function MediaUploader({
|
|
|
1552
1621
|
children: "Cancel"
|
|
1553
1622
|
}
|
|
1554
1623
|
)
|
|
1555
|
-
] }),
|
|
1556
|
-
state.phase === "done" && /* @__PURE__ */ jsx23("div", { className: "text-sm text-green-500", children: "\u2713 Uploaded" }),
|
|
1557
|
-
state.phase === "error" && /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1624
|
+
] }) : state.phase === "error" ? /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1558
1625
|
/* @__PURE__ */ jsx23("div", { className: "text-sm text-red-500", children: state.message }),
|
|
1559
1626
|
/* @__PURE__ */ jsx23(
|
|
1560
1627
|
"button",
|
|
@@ -1568,30 +1635,23 @@ function MediaUploader({
|
|
|
1568
1635
|
children: "Try again"
|
|
1569
1636
|
}
|
|
1570
1637
|
)
|
|
1571
|
-
] })
|
|
1638
|
+
] }) : null
|
|
1572
1639
|
]
|
|
1573
1640
|
}
|
|
1574
1641
|
),
|
|
1575
|
-
/* @__PURE__ */
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
"img",
|
|
1589
|
-
{
|
|
1590
|
-
src: value,
|
|
1591
|
-
alt: "current value preview",
|
|
1592
|
-
className: "max-w-[64px] max-h-[64px] rounded-[--radius-sm] object-cover border border-[--border] mt-2"
|
|
1593
|
-
}
|
|
1594
|
-
)
|
|
1642
|
+
!deferUpload && /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1643
|
+
/* @__PURE__ */ jsx23("div", { className: "text-xs text-neutral-500 dark:text-white/45", children: "or paste URL:" }),
|
|
1644
|
+
/* @__PURE__ */ jsx23(
|
|
1645
|
+
Input,
|
|
1646
|
+
{
|
|
1647
|
+
type: "url",
|
|
1648
|
+
placeholder: "https://...",
|
|
1649
|
+
value: urlInput,
|
|
1650
|
+
onChange: (e) => setUrlInput(e.target.value),
|
|
1651
|
+
onBlur: () => urlInput && onChange(urlInput)
|
|
1652
|
+
}
|
|
1653
|
+
)
|
|
1654
|
+
] })
|
|
1595
1655
|
] });
|
|
1596
1656
|
}
|
|
1597
1657
|
|
|
@@ -1615,7 +1675,7 @@ function SortableTile({ item, onRemove }) {
|
|
|
1615
1675
|
transform: CSS.Transform.toString(transform),
|
|
1616
1676
|
transition
|
|
1617
1677
|
};
|
|
1618
|
-
return /* @__PURE__ */ jsxs19("div", { ref: setNodeRef, style, className: "relative w-24 h-24 rounded-
|
|
1678
|
+
return /* @__PURE__ */ jsxs19("div", { ref: setNodeRef, style, className: "relative w-24 h-24 rounded-lg border border-neutral-200 dark:border-white/8 overflow-hidden", children: [
|
|
1619
1679
|
/* @__PURE__ */ jsx24("button", { ...attributes, ...listeners, "aria-label": "drag handle", className: "absolute top-1 left-1 z-10 text-xs opacity-70 hover:opacity-100", children: "\u283F" }),
|
|
1620
1680
|
/* @__PURE__ */ jsx24("img", { src: item.url, alt: `${item.type} thumbnail`, className: "w-full h-full object-cover" }),
|
|
1621
1681
|
/* @__PURE__ */ jsx24(
|
|
@@ -1645,7 +1705,7 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1645
1705
|
onDragOver: (e) => e.preventDefault(),
|
|
1646
1706
|
onDrop: (e) => e.preventDefault(),
|
|
1647
1707
|
children: [
|
|
1648
|
-
/* @__PURE__ */ jsxs19("div", { className: "text-sm text-
|
|
1708
|
+
/* @__PURE__ */ jsxs19("div", { className: "text-sm text-neutral-700 dark:text-white/70", children: [
|
|
1649
1709
|
"Screenshots (",
|
|
1650
1710
|
items.length,
|
|
1651
1711
|
"/",
|
|
@@ -1667,12 +1727,12 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1667
1727
|
type: "button",
|
|
1668
1728
|
"aria-label": "add screenshot",
|
|
1669
1729
|
onClick: () => setAdding(true),
|
|
1670
|
-
className: "w-24 h-24 rounded-
|
|
1730
|
+
className: "w-24 h-24 rounded-lg border-2 border-dashed border-neutral-200 dark:border-white/8 text-2xl hover:border-orange-500 dark:hover:border-brand-orange",
|
|
1671
1731
|
children: "+"
|
|
1672
1732
|
}
|
|
1673
1733
|
)
|
|
1674
1734
|
] }) }) }),
|
|
1675
|
-
adding && /* @__PURE__ */ jsxs19("div", { className: "border border-
|
|
1735
|
+
adding && /* @__PURE__ */ jsxs19("div", { className: "border border-neutral-200 dark:border-white/8 rounded-lg p-3", children: [
|
|
1676
1736
|
/* @__PURE__ */ jsx24(
|
|
1677
1737
|
MediaUploader,
|
|
1678
1738
|
{
|
|
@@ -1700,77 +1760,598 @@ function MediaGallery({ ownerType, ownerId, items, onChange, uploadFn, max = 10
|
|
|
1700
1760
|
}
|
|
1701
1761
|
|
|
1702
1762
|
// src/components/media/MarketplaceProjectCard.tsx
|
|
1703
|
-
import {
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1763
|
+
import { motion } from "framer-motion";
|
|
1764
|
+
import { Users, Target, ThumbsUp, Plus, Check } from "lucide-react";
|
|
1765
|
+
import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1766
|
+
var categoryLabels = {
|
|
1767
|
+
game: "Game",
|
|
1768
|
+
defi: "DeFi",
|
|
1769
|
+
social: "Social",
|
|
1770
|
+
tool: "Tool",
|
|
1771
|
+
nft: "NFT",
|
|
1772
|
+
other: "Other"
|
|
1773
|
+
};
|
|
1712
1774
|
function MarketplaceProjectCard({
|
|
1713
1775
|
name,
|
|
1714
1776
|
tagline,
|
|
1715
1777
|
logoUrl,
|
|
1716
1778
|
bannerUrl,
|
|
1717
|
-
|
|
1718
|
-
|
|
1779
|
+
accentColor = "#FF6F00",
|
|
1780
|
+
category,
|
|
1781
|
+
users = 0,
|
|
1782
|
+
quests = 0,
|
|
1783
|
+
positivePercent = 0,
|
|
1784
|
+
ratingCount = 0,
|
|
1785
|
+
installState = "none",
|
|
1786
|
+
onInstallClick,
|
|
1787
|
+
onClick
|
|
1719
1788
|
}) {
|
|
1720
1789
|
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
|
-
|
|
1790
|
+
const installed = installState === "installed";
|
|
1791
|
+
const showInstall = installState !== "none";
|
|
1792
|
+
const handleInstall = (e) => {
|
|
1793
|
+
e.preventDefault();
|
|
1794
|
+
e.stopPropagation();
|
|
1795
|
+
onInstallClick?.();
|
|
1796
|
+
};
|
|
1797
|
+
const placeholderLogo = `https://placehold.co/44x44/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
1798
|
+
const card = /* @__PURE__ */ jsxs20(
|
|
1799
|
+
motion.div,
|
|
1800
|
+
{
|
|
1801
|
+
whileHover: { y: -4 },
|
|
1802
|
+
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",
|
|
1803
|
+
children: [
|
|
1804
|
+
/* @__PURE__ */ jsxs20("div", { className: "relative h-24 overflow-hidden", "data-testid": "banner", children: [
|
|
1805
|
+
hasBanner ? /* @__PURE__ */ jsxs20(Fragment5, { children: [
|
|
1806
|
+
/* @__PURE__ */ jsx25(
|
|
1807
|
+
"div",
|
|
1808
|
+
{
|
|
1809
|
+
className: "absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105",
|
|
1810
|
+
style: { backgroundImage: `url(${bannerUrl})` }
|
|
1811
|
+
}
|
|
1812
|
+
),
|
|
1813
|
+
/* @__PURE__ */ jsx25("div", { className: "absolute inset-0", style: {
|
|
1814
|
+
background: `linear-gradient(to bottom, ${accentColor}33 0%, ${accentColor}99 100%)`
|
|
1815
|
+
} })
|
|
1816
|
+
] }) : /* @__PURE__ */ jsx25("div", { className: "absolute inset-0", style: {
|
|
1817
|
+
background: `linear-gradient(135deg, ${accentColor}cc 0%, ${accentColor}44 100%)`
|
|
1818
|
+
} }),
|
|
1819
|
+
showInstall && /* @__PURE__ */ jsx25(
|
|
1820
|
+
"button",
|
|
1821
|
+
{
|
|
1822
|
+
onClick: handleInstall,
|
|
1823
|
+
title: installed ? "Remove from Desktop" : "Add to Desktop",
|
|
1824
|
+
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"}`,
|
|
1825
|
+
children: installed ? /* @__PURE__ */ jsx25(Check, { className: "w-4 h-4" }) : /* @__PURE__ */ jsx25(Plus, { className: "w-4 h-4" })
|
|
1826
|
+
}
|
|
1827
|
+
)
|
|
1828
|
+
] }),
|
|
1829
|
+
/* @__PURE__ */ jsxs20("div", { className: "p-4 bg-white dark:bg-white/4 dark:backdrop-blur-2xl", children: [
|
|
1830
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-start gap-3", children: [
|
|
1831
|
+
/* @__PURE__ */ jsx25(
|
|
1832
|
+
"img",
|
|
1833
|
+
{
|
|
1834
|
+
src: logoUrl ?? placeholderLogo,
|
|
1835
|
+
alt: name,
|
|
1836
|
+
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",
|
|
1837
|
+
onError: (e) => {
|
|
1838
|
+
e.target.src = placeholderLogo;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
),
|
|
1842
|
+
/* @__PURE__ */ jsxs20("div", { className: "min-w-0 flex-1", children: [
|
|
1843
|
+
/* @__PURE__ */ jsx25("h3", { className: "font-semibold text-neutral-900 dark:text-white text-sm truncate", children: name }),
|
|
1844
|
+
/* @__PURE__ */ jsx25("p", { className: "text-neutral-500 dark:text-white/45 text-xs mt-0.5 h-8 line-clamp-2", children: tagline })
|
|
1845
|
+
] })
|
|
1846
|
+
] }),
|
|
1847
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between mt-3 pt-3 border-t border-neutral-100 dark:border-white/5", children: [
|
|
1848
|
+
category ? /* @__PURE__ */ jsx25("span", { className: "inline-flex px-2 py-0.5 rounded-md text-[10px] font-semibold uppercase tracking-wider border", style: {
|
|
1849
|
+
backgroundColor: `${accentColor}15`,
|
|
1850
|
+
color: accentColor,
|
|
1851
|
+
borderColor: `${accentColor}30`
|
|
1852
|
+
}, children: categoryLabels[category] ?? category }) : /* @__PURE__ */ jsx25("span", {}),
|
|
1853
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-3 text-[11px] text-neutral-400 dark:text-white/35", children: [
|
|
1854
|
+
/* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: "Users", children: [
|
|
1855
|
+
/* @__PURE__ */ jsx25(Users, { className: "w-3 h-3" }),
|
|
1856
|
+
users.toLocaleString()
|
|
1857
|
+
] }),
|
|
1858
|
+
/* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: "Active quests", children: [
|
|
1859
|
+
/* @__PURE__ */ jsx25(Target, { className: "w-3 h-3" }),
|
|
1860
|
+
quests.toLocaleString()
|
|
1861
|
+
] }),
|
|
1862
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs20("span", { className: "flex items-center gap-1", title: `${ratingCount} reviews`, children: [
|
|
1863
|
+
/* @__PURE__ */ jsx25(ThumbsUp, { className: "w-3 h-3" }),
|
|
1864
|
+
positivePercent,
|
|
1865
|
+
"%"
|
|
1866
|
+
] })
|
|
1867
|
+
] })
|
|
1868
|
+
] })
|
|
1869
|
+
] })
|
|
1870
|
+
]
|
|
1871
|
+
}
|
|
1872
|
+
);
|
|
1873
|
+
if (onClick) {
|
|
1874
|
+
return /* @__PURE__ */ jsx25("button", { type: "button", onClick, className: "block w-full text-left", children: card });
|
|
1875
|
+
}
|
|
1876
|
+
return card;
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
// src/components/media/FeaturedProjectCard.tsx
|
|
1880
|
+
import { motion as motion2 } from "framer-motion";
|
|
1881
|
+
import { Star, Users as Users2, Target as Target2, ThumbsUp as ThumbsUp2 } from "lucide-react";
|
|
1882
|
+
import { jsx as jsx26, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1883
|
+
function FeaturedProjectCard({
|
|
1884
|
+
name,
|
|
1885
|
+
tagline,
|
|
1886
|
+
logoUrl,
|
|
1887
|
+
bannerUrl,
|
|
1888
|
+
accentColor = "#FF6F00",
|
|
1889
|
+
users = 0,
|
|
1890
|
+
quests = 0,
|
|
1891
|
+
positivePercent = 0,
|
|
1892
|
+
ratingCount = 0,
|
|
1893
|
+
onClick
|
|
1894
|
+
}) {
|
|
1895
|
+
const placeholderLogo = `https://placehold.co/40x40/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
1896
|
+
const card = /* @__PURE__ */ jsxs21(
|
|
1897
|
+
motion2.div,
|
|
1898
|
+
{
|
|
1899
|
+
whileHover: { scale: 1.02, y: -2 },
|
|
1900
|
+
whileTap: { scale: 0.98 },
|
|
1901
|
+
className: "relative w-72 sm:w-80 h-44 rounded-2xl overflow-hidden shrink-0 cursor-pointer group",
|
|
1902
|
+
children: [
|
|
1903
|
+
/* @__PURE__ */ jsx26(
|
|
1749
1904
|
"div",
|
|
1750
1905
|
{
|
|
1751
|
-
className:
|
|
1752
|
-
|
|
1906
|
+
className: "absolute inset-0 bg-cover bg-center transition-transform duration-500 group-hover:scale-105",
|
|
1907
|
+
"data-testid": "banner",
|
|
1908
|
+
style: {
|
|
1909
|
+
backgroundColor: accentColor,
|
|
1910
|
+
backgroundImage: bannerUrl ? `url(${bannerUrl})` : void 0
|
|
1911
|
+
}
|
|
1753
1912
|
}
|
|
1754
1913
|
),
|
|
1755
|
-
/* @__PURE__ */
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
/* @__PURE__ */ jsx25("span", { className: "text-[--accent]", children: "\u2605" }),
|
|
1763
|
-
" ",
|
|
1764
|
-
rating.toFixed(1)
|
|
1914
|
+
/* @__PURE__ */ jsx26("div", { className: "absolute inset-0", style: {
|
|
1915
|
+
background: bannerUrl ? `linear-gradient(135deg, ${accentColor}66 0%, transparent 60%)` : `linear-gradient(135deg, ${accentColor}cc 0%, ${accentColor}44 100%)`
|
|
1916
|
+
} }),
|
|
1917
|
+
/* @__PURE__ */ jsx26("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent" }),
|
|
1918
|
+
/* @__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: [
|
|
1919
|
+
/* @__PURE__ */ jsx26(Star, { className: "w-3 h-3", fill: "currentColor" }),
|
|
1920
|
+
"Featured"
|
|
1765
1921
|
] }),
|
|
1766
|
-
|
|
1767
|
-
"
|
|
1768
|
-
|
|
1769
|
-
|
|
1922
|
+
/* @__PURE__ */ jsxs21("div", { className: "absolute bottom-0 left-0 right-0 p-4", children: [
|
|
1923
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-3", children: [
|
|
1924
|
+
/* @__PURE__ */ jsx26(
|
|
1925
|
+
"img",
|
|
1926
|
+
{
|
|
1927
|
+
src: logoUrl ?? placeholderLogo,
|
|
1928
|
+
alt: name,
|
|
1929
|
+
className: "w-10 h-10 rounded-xl object-cover border-2 border-white/20 shadow-lg",
|
|
1930
|
+
onError: (e) => {
|
|
1931
|
+
e.target.src = placeholderLogo;
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
),
|
|
1935
|
+
/* @__PURE__ */ jsxs21("div", { className: "min-w-0", children: [
|
|
1936
|
+
/* @__PURE__ */ jsx26("h3", { className: "font-semibold text-white text-sm truncate", children: name }),
|
|
1937
|
+
/* @__PURE__ */ jsx26("p", { className: "text-white/70 text-xs truncate", children: tagline })
|
|
1938
|
+
] })
|
|
1939
|
+
] }),
|
|
1940
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-3 mt-2 text-[11px] text-white/60", children: [
|
|
1941
|
+
/* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: "Users", children: [
|
|
1942
|
+
/* @__PURE__ */ jsx26(Users2, { className: "w-3 h-3" }),
|
|
1943
|
+
users.toLocaleString()
|
|
1944
|
+
] }),
|
|
1945
|
+
/* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: "Active quests", children: [
|
|
1946
|
+
/* @__PURE__ */ jsx26(Target2, { className: "w-3 h-3" }),
|
|
1947
|
+
quests.toLocaleString()
|
|
1948
|
+
] }),
|
|
1949
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs21("span", { className: "flex items-center gap-1", title: `${ratingCount} reviews`, children: [
|
|
1950
|
+
/* @__PURE__ */ jsx26(ThumbsUp2, { className: "w-3 h-3" }),
|
|
1951
|
+
positivePercent,
|
|
1952
|
+
"%"
|
|
1953
|
+
] })
|
|
1954
|
+
] })
|
|
1770
1955
|
] })
|
|
1771
|
-
]
|
|
1772
|
-
|
|
1773
|
-
|
|
1956
|
+
]
|
|
1957
|
+
}
|
|
1958
|
+
);
|
|
1959
|
+
if (onClick) {
|
|
1960
|
+
return /* @__PURE__ */ jsx26("button", { type: "button", onClick, className: "block text-left", draggable: false, children: card });
|
|
1961
|
+
}
|
|
1962
|
+
return card;
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// src/components/media/InstalledProjectIcon.tsx
|
|
1966
|
+
import { useState as useState9 } from "react";
|
|
1967
|
+
import { motion as motion3 } from "framer-motion";
|
|
1968
|
+
import { jsx as jsx27, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1969
|
+
function InstalledProjectIcon({
|
|
1970
|
+
name,
|
|
1971
|
+
logoUrl,
|
|
1972
|
+
accentColor = "#FF6F00",
|
|
1973
|
+
onClick,
|
|
1974
|
+
showLabel = true
|
|
1975
|
+
}) {
|
|
1976
|
+
const [imgError, setImgError] = useState9(false);
|
|
1977
|
+
return /* @__PURE__ */ jsx27("div", { className: "relative", children: /* @__PURE__ */ jsxs22(
|
|
1978
|
+
motion3.button,
|
|
1979
|
+
{
|
|
1980
|
+
type: "button",
|
|
1981
|
+
onClick,
|
|
1982
|
+
whileHover: { scale: 1.08, y: -4 },
|
|
1983
|
+
whileTap: { scale: 0.92 },
|
|
1984
|
+
transition: { duration: 0.05 },
|
|
1985
|
+
className: "flex flex-col items-center gap-2 p-3 rounded-2xl group cursor-pointer relative",
|
|
1986
|
+
children: [
|
|
1987
|
+
/* @__PURE__ */ jsxs22("div", { className: "relative", children: [
|
|
1988
|
+
/* @__PURE__ */ jsx27(
|
|
1989
|
+
"div",
|
|
1990
|
+
{
|
|
1991
|
+
className: "absolute -inset-1 blur-xl opacity-0 group-hover:opacity-50 transition-all duration-300 rounded-2xl",
|
|
1992
|
+
style: { backgroundColor: accentColor }
|
|
1993
|
+
}
|
|
1994
|
+
),
|
|
1995
|
+
/* @__PURE__ */ jsxs22(
|
|
1996
|
+
"div",
|
|
1997
|
+
{
|
|
1998
|
+
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",
|
|
1999
|
+
style: { background: `linear-gradient(135deg, ${accentColor}, ${accentColor}cc)` },
|
|
2000
|
+
children: [
|
|
2001
|
+
/* @__PURE__ */ jsx27(
|
|
2002
|
+
"div",
|
|
2003
|
+
{
|
|
2004
|
+
className: "absolute inset-0 opacity-30 group-hover:opacity-50 transition-opacity duration-500",
|
|
2005
|
+
style: {
|
|
2006
|
+
backgroundImage: `radial-gradient(at 27% 37%, rgba(255,255,255,0.15) 0px, transparent 50%),
|
|
2007
|
+
radial-gradient(at 97% 21%, rgba(255,255,255,0.1) 0px, transparent 50%)`
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
),
|
|
2011
|
+
/* @__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" }),
|
|
2012
|
+
logoUrl && !imgError ? /* @__PURE__ */ jsx27(
|
|
2013
|
+
"img",
|
|
2014
|
+
{
|
|
2015
|
+
src: logoUrl,
|
|
2016
|
+
alt: name,
|
|
2017
|
+
onError: () => setImgError(true),
|
|
2018
|
+
className: "w-9 h-9 sm:w-10 sm:h-10 object-contain rounded-lg relative z-10 drop-shadow-lg"
|
|
2019
|
+
}
|
|
2020
|
+
) : /* @__PURE__ */ jsx27("span", { className: "text-white font-bold text-lg relative z-10", children: name[0] ?? "?" })
|
|
2021
|
+
]
|
|
2022
|
+
}
|
|
2023
|
+
)
|
|
2024
|
+
] }),
|
|
2025
|
+
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 })
|
|
2026
|
+
]
|
|
2027
|
+
}
|
|
2028
|
+
) });
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// src/components/media/ProjectPagePreview.tsx
|
|
2032
|
+
import { motion as motion4 } from "framer-motion";
|
|
2033
|
+
import {
|
|
2034
|
+
ArrowLeft,
|
|
2035
|
+
ExternalLink,
|
|
2036
|
+
Users as Users3,
|
|
2037
|
+
Target as Target3,
|
|
2038
|
+
ThumbsUp as ThumbsUp3,
|
|
2039
|
+
Globe,
|
|
2040
|
+
Plus as Plus2,
|
|
2041
|
+
Star as Star2,
|
|
2042
|
+
Trophy,
|
|
2043
|
+
MessageCircle,
|
|
2044
|
+
Twitter,
|
|
2045
|
+
Zap
|
|
2046
|
+
} from "lucide-react";
|
|
2047
|
+
import { jsx as jsx28, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2048
|
+
var categoryLabels2 = {
|
|
2049
|
+
game: "Game",
|
|
2050
|
+
defi: "DeFi",
|
|
2051
|
+
social: "Social",
|
|
2052
|
+
tool: "Tool",
|
|
2053
|
+
nft: "NFT",
|
|
2054
|
+
other: "Other"
|
|
2055
|
+
};
|
|
2056
|
+
var difficultyStyles = {
|
|
2057
|
+
easy: "bg-green-500/10 text-green-600 dark:text-green-400 border-green-500/25",
|
|
2058
|
+
medium: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/25",
|
|
2059
|
+
hard: "bg-red-500/10 text-red-600 dark:text-red-400 border-red-500/25"
|
|
2060
|
+
};
|
|
2061
|
+
function getMediaThumb(m) {
|
|
2062
|
+
if (m.type !== "video") return m.url;
|
|
2063
|
+
const ytMatch = m.url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\s]+)/);
|
|
2064
|
+
return ytMatch ? `https://img.youtube.com/vi/${ytMatch[1]}/hqdefault.jpg` : m.url;
|
|
2065
|
+
}
|
|
2066
|
+
function ProjectPagePreview({
|
|
2067
|
+
name,
|
|
2068
|
+
tagline,
|
|
2069
|
+
description,
|
|
2070
|
+
logoUrl,
|
|
2071
|
+
bannerUrl,
|
|
2072
|
+
accentColor = "#FF6F00",
|
|
2073
|
+
category,
|
|
2074
|
+
websiteUrl,
|
|
2075
|
+
discordUrl,
|
|
2076
|
+
twitterUrl,
|
|
2077
|
+
media = [],
|
|
2078
|
+
users = 0,
|
|
2079
|
+
activeQuests = 0,
|
|
2080
|
+
positivePercent = 0,
|
|
2081
|
+
ratingCount = 0,
|
|
2082
|
+
quests = [],
|
|
2083
|
+
achievements = [],
|
|
2084
|
+
tags = []
|
|
2085
|
+
}) {
|
|
2086
|
+
const placeholderLogo = `https://placehold.co/80x80/${accentColor.slice(1)}/white?text=${name[0] ?? "?"}`;
|
|
2087
|
+
return /* @__PURE__ */ jsxs23(
|
|
2088
|
+
motion4.div,
|
|
2089
|
+
{
|
|
2090
|
+
initial: { opacity: 0 },
|
|
2091
|
+
animate: { opacity: 1 },
|
|
2092
|
+
className: "text-neutral-900 dark:text-white pb-12",
|
|
2093
|
+
children: [
|
|
2094
|
+
/* @__PURE__ */ jsxs23("div", { className: "relative mx-4 sm:mx-6 mt-2", children: [
|
|
2095
|
+
/* @__PURE__ */ jsxs23("div", { className: "relative h-48 sm:h-64 rounded-2xl overflow-hidden", children: [
|
|
2096
|
+
/* @__PURE__ */ jsx28(
|
|
2097
|
+
"div",
|
|
2098
|
+
{
|
|
2099
|
+
className: "absolute inset-0 bg-cover bg-center",
|
|
2100
|
+
"data-testid": "banner",
|
|
2101
|
+
style: {
|
|
2102
|
+
backgroundColor: accentColor,
|
|
2103
|
+
backgroundImage: bannerUrl ? `url(${bannerUrl})` : void 0
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
),
|
|
2107
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute inset-0 bg-gradient-to-t from-black/70 via-black/20 to-transparent" }),
|
|
2108
|
+
/* @__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: [
|
|
2109
|
+
/* @__PURE__ */ jsx28(ArrowLeft, { className: "w-4 h-4" }),
|
|
2110
|
+
"Explore"
|
|
2111
|
+
] })
|
|
2112
|
+
] }),
|
|
2113
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute -bottom-6 left-6 sm:left-8 z-10", children: /* @__PURE__ */ jsx28(
|
|
2114
|
+
"img",
|
|
2115
|
+
{
|
|
2116
|
+
src: logoUrl ?? placeholderLogo,
|
|
2117
|
+
alt: name,
|
|
2118
|
+
className: "w-16 h-16 sm:w-20 sm:h-20 rounded-2xl object-cover border-4 border-white dark:border-[#060606] shadow-xl",
|
|
2119
|
+
onError: (e) => {
|
|
2120
|
+
e.target.src = placeholderLogo;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
) })
|
|
2124
|
+
] }),
|
|
2125
|
+
/* @__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: [
|
|
2126
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4", children: [
|
|
2127
|
+
/* @__PURE__ */ jsxs23("div", { children: [
|
|
2128
|
+
/* @__PURE__ */ jsx28("h1", { className: "text-2xl sm:text-3xl font-bold", children: name }),
|
|
2129
|
+
tagline && /* @__PURE__ */ jsx28("p", { className: "text-neutral-500 dark:text-white/55 mt-1", children: tagline }),
|
|
2130
|
+
(category || tags.length > 0) && /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2 mt-3", children: [
|
|
2131
|
+
category && /* @__PURE__ */ jsx28(
|
|
2132
|
+
"span",
|
|
2133
|
+
{
|
|
2134
|
+
className: "inline-flex px-2.5 py-0.5 rounded-lg text-xs font-semibold uppercase tracking-wider",
|
|
2135
|
+
style: {
|
|
2136
|
+
backgroundColor: `${accentColor}15`,
|
|
2137
|
+
color: accentColor,
|
|
2138
|
+
border: `1px solid ${accentColor}30`
|
|
2139
|
+
},
|
|
2140
|
+
children: categoryLabels2[category] ?? category
|
|
2141
|
+
}
|
|
2142
|
+
),
|
|
2143
|
+
tags.slice(0, 3).map((tag) => /* @__PURE__ */ jsx28(
|
|
2144
|
+
"span",
|
|
2145
|
+
{
|
|
2146
|
+
className: "px-2 py-0.5 rounded-md bg-neutral-100 dark:bg-white/6 text-neutral-500 dark:text-white/40 text-[10px] font-mono",
|
|
2147
|
+
children: tag
|
|
2148
|
+
},
|
|
2149
|
+
tag
|
|
2150
|
+
))
|
|
2151
|
+
] })
|
|
2152
|
+
] }),
|
|
2153
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex gap-2 shrink-0 flex-wrap", children: [
|
|
2154
|
+
/* @__PURE__ */ jsxs23(
|
|
2155
|
+
"button",
|
|
2156
|
+
{
|
|
2157
|
+
type: "button",
|
|
2158
|
+
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",
|
|
2159
|
+
children: [
|
|
2160
|
+
/* @__PURE__ */ jsx28(Plus2, { className: "w-4 h-4" }),
|
|
2161
|
+
" Add to Desktop"
|
|
2162
|
+
]
|
|
2163
|
+
}
|
|
2164
|
+
),
|
|
2165
|
+
/* @__PURE__ */ jsxs23(
|
|
2166
|
+
"button",
|
|
2167
|
+
{
|
|
2168
|
+
type: "button",
|
|
2169
|
+
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",
|
|
2170
|
+
children: [
|
|
2171
|
+
"Open ",
|
|
2172
|
+
/* @__PURE__ */ jsx28(ExternalLink, { className: "w-3.5 h-3.5" })
|
|
2173
|
+
]
|
|
2174
|
+
}
|
|
2175
|
+
),
|
|
2176
|
+
websiteUrl && /* @__PURE__ */ jsxs23(
|
|
2177
|
+
"a",
|
|
2178
|
+
{
|
|
2179
|
+
href: websiteUrl,
|
|
2180
|
+
target: "_blank",
|
|
2181
|
+
rel: "noopener noreferrer",
|
|
2182
|
+
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",
|
|
2183
|
+
children: [
|
|
2184
|
+
"Website ",
|
|
2185
|
+
/* @__PURE__ */ jsx28(Globe, { className: "w-3.5 h-3.5" })
|
|
2186
|
+
]
|
|
2187
|
+
}
|
|
2188
|
+
)
|
|
2189
|
+
] })
|
|
2190
|
+
] }),
|
|
2191
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex gap-6 sm:gap-10 border-t border-neutral-200 dark:border-white/8 mt-6 pt-5", children: [
|
|
2192
|
+
/* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2193
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2194
|
+
/* @__PURE__ */ jsx28(Users3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2195
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: users.toLocaleString() })
|
|
2196
|
+
] }),
|
|
2197
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: "Users" })
|
|
2198
|
+
] }),
|
|
2199
|
+
/* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2200
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2201
|
+
/* @__PURE__ */ jsx28(Target3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2202
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: activeQuests.toLocaleString() })
|
|
2203
|
+
] }),
|
|
2204
|
+
/* @__PURE__ */ jsx28("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: "Active Quests" })
|
|
2205
|
+
] }),
|
|
2206
|
+
ratingCount > 0 && /* @__PURE__ */ jsxs23("div", { className: "text-center sm:text-left", children: [
|
|
2207
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1.5 justify-center sm:justify-start", children: [
|
|
2208
|
+
/* @__PURE__ */ jsx28(ThumbsUp3, { className: "w-4 h-4 text-neutral-400 dark:text-white/30" }),
|
|
2209
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-xl sm:text-2xl font-bold font-mono", children: [
|
|
2210
|
+
positivePercent,
|
|
2211
|
+
"%"
|
|
2212
|
+
] })
|
|
2213
|
+
] }),
|
|
2214
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-xs text-neutral-400 dark:text-white/35 uppercase tracking-wider", children: [
|
|
2215
|
+
ratingCount.toLocaleString(),
|
|
2216
|
+
" ",
|
|
2217
|
+
ratingCount === 1 ? "review" : "reviews"
|
|
2218
|
+
] })
|
|
2219
|
+
] })
|
|
2220
|
+
] })
|
|
2221
|
+
] }) }),
|
|
2222
|
+
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: [
|
|
2223
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "Media" }),
|
|
2224
|
+
/* @__PURE__ */ jsx28("div", { className: "flex gap-3 overflow-x-auto scrollbar-hide pb-2", children: media.map((item, i) => {
|
|
2225
|
+
const isVideo = item.type === "video";
|
|
2226
|
+
return /* @__PURE__ */ jsxs23(
|
|
2227
|
+
"div",
|
|
2228
|
+
{
|
|
2229
|
+
className: "shrink-0 rounded-xl overflow-hidden border border-neutral-200 dark:border-white/8 relative",
|
|
2230
|
+
children: [
|
|
2231
|
+
/* @__PURE__ */ jsx28(
|
|
2232
|
+
"img",
|
|
2233
|
+
{
|
|
2234
|
+
src: getMediaThumb(item),
|
|
2235
|
+
alt: isVideo ? `Video ${i + 1}` : `Screenshot ${i + 1}`,
|
|
2236
|
+
draggable: false,
|
|
2237
|
+
className: "h-44 sm:h-52 w-72 sm:w-80 object-cover"
|
|
2238
|
+
}
|
|
2239
|
+
),
|
|
2240
|
+
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" }) }) }) })
|
|
2241
|
+
]
|
|
2242
|
+
},
|
|
2243
|
+
i
|
|
2244
|
+
);
|
|
2245
|
+
}) })
|
|
2246
|
+
] }) }),
|
|
2247
|
+
description && /* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2248
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "About" }),
|
|
2249
|
+
/* @__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 }) })
|
|
2250
|
+
] }) }),
|
|
2251
|
+
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: [
|
|
2252
|
+
/* @__PURE__ */ jsxs23("h2", { className: "text-lg font-semibold mb-4", children: [
|
|
2253
|
+
"Quests ",
|
|
2254
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-neutral-400 dark:text-white/35 font-normal", children: [
|
|
2255
|
+
"(",
|
|
2256
|
+
quests.length,
|
|
2257
|
+
")"
|
|
2258
|
+
] })
|
|
2259
|
+
] }),
|
|
2260
|
+
/* @__PURE__ */ jsx28("div", { className: "grid sm:grid-cols-2 gap-3", children: quests.map((quest) => /* @__PURE__ */ jsxs23(
|
|
2261
|
+
"div",
|
|
2262
|
+
{
|
|
2263
|
+
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",
|
|
2264
|
+
children: [
|
|
2265
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute left-0 top-0 bottom-0 w-0.5", style: { backgroundColor: accentColor } }),
|
|
2266
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-start gap-3", children: [
|
|
2267
|
+
/* @__PURE__ */ jsxs23("div", { className: "min-w-0 flex-1", children: [
|
|
2268
|
+
/* @__PURE__ */ jsx28("h4", { className: "font-medium text-sm text-neutral-900 dark:text-white", children: quest.title }),
|
|
2269
|
+
quest.description && /* @__PURE__ */ jsx28("p", { className: "text-xs text-neutral-500 dark:text-white/40 mt-0.5 line-clamp-2", children: quest.description }),
|
|
2270
|
+
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 })
|
|
2271
|
+
] }),
|
|
2272
|
+
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: [
|
|
2273
|
+
/* @__PURE__ */ jsx28(Zap, { className: "w-3 h-3" }),
|
|
2274
|
+
quest.points
|
|
2275
|
+
] })
|
|
2276
|
+
] })
|
|
2277
|
+
]
|
|
2278
|
+
},
|
|
2279
|
+
quest.slug
|
|
2280
|
+
)) })
|
|
2281
|
+
] }) }),
|
|
2282
|
+
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: [
|
|
2283
|
+
/* @__PURE__ */ jsxs23("h2", { className: "text-lg font-semibold mb-4", children: [
|
|
2284
|
+
"Achievements ",
|
|
2285
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-neutral-400 dark:text-white/35 font-normal", children: [
|
|
2286
|
+
"(",
|
|
2287
|
+
achievements.length,
|
|
2288
|
+
")"
|
|
2289
|
+
] })
|
|
2290
|
+
] }),
|
|
2291
|
+
/* @__PURE__ */ jsx28("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3", children: achievements.map((ach) => /* @__PURE__ */ jsxs23(
|
|
2292
|
+
"div",
|
|
2293
|
+
{
|
|
2294
|
+
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",
|
|
2295
|
+
children: [
|
|
2296
|
+
/* @__PURE__ */ jsx28("div", { className: "w-16 h-16 rounded-xl overflow-hidden flex items-center justify-center mb-3", style: {
|
|
2297
|
+
backgroundColor: `${accentColor}15`,
|
|
2298
|
+
border: `1px solid ${accentColor}30`
|
|
2299
|
+
}, 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 } }) }),
|
|
2300
|
+
/* @__PURE__ */ jsx28("h4", { className: "text-sm font-medium text-neutral-900 dark:text-white line-clamp-2", children: ach.title }),
|
|
2301
|
+
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: [
|
|
2302
|
+
/* @__PURE__ */ jsx28(Zap, { className: "w-3 h-3" }),
|
|
2303
|
+
ach.points
|
|
2304
|
+
] })
|
|
2305
|
+
]
|
|
2306
|
+
},
|
|
2307
|
+
ach.slug
|
|
2308
|
+
)) })
|
|
2309
|
+
] }) }),
|
|
2310
|
+
/* @__PURE__ */ jsx28("section", { className: "px-4 sm:px-6 pb-8", children: /* @__PURE__ */ jsxs23("div", { className: "max-w-5xl mx-auto", children: [
|
|
2311
|
+
/* @__PURE__ */ jsx28("h2", { className: "text-lg font-semibold mb-4", children: "Reviews" }),
|
|
2312
|
+
/* @__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: [
|
|
2313
|
+
/* @__PURE__ */ jsx28(Star2, { className: "w-8 h-8 text-neutral-300 dark:text-white/20 mb-2" }),
|
|
2314
|
+
/* @__PURE__ */ jsx28("p", { className: "text-sm text-neutral-500 dark:text-white/40", children: "Reviews coming soon" })
|
|
2315
|
+
] })
|
|
2316
|
+
] }) }),
|
|
2317
|
+
(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: [
|
|
2318
|
+
websiteUrl && /* @__PURE__ */ jsx28(
|
|
2319
|
+
"a",
|
|
2320
|
+
{
|
|
2321
|
+
href: websiteUrl,
|
|
2322
|
+
target: "_blank",
|
|
2323
|
+
rel: "noopener noreferrer",
|
|
2324
|
+
"aria-label": "Website",
|
|
2325
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2326
|
+
children: /* @__PURE__ */ jsx28(Globe, { className: "w-5 h-5" })
|
|
2327
|
+
}
|
|
2328
|
+
),
|
|
2329
|
+
twitterUrl && /* @__PURE__ */ jsx28(
|
|
2330
|
+
"a",
|
|
2331
|
+
{
|
|
2332
|
+
href: twitterUrl,
|
|
2333
|
+
target: "_blank",
|
|
2334
|
+
rel: "noopener noreferrer",
|
|
2335
|
+
"aria-label": "Twitter / X",
|
|
2336
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2337
|
+
children: /* @__PURE__ */ jsx28(Twitter, { className: "w-5 h-5" })
|
|
2338
|
+
}
|
|
2339
|
+
),
|
|
2340
|
+
discordUrl && /* @__PURE__ */ jsx28(
|
|
2341
|
+
"a",
|
|
2342
|
+
{
|
|
2343
|
+
href: discordUrl,
|
|
2344
|
+
target: "_blank",
|
|
2345
|
+
rel: "noopener noreferrer",
|
|
2346
|
+
"aria-label": "Discord",
|
|
2347
|
+
className: "text-neutral-400 dark:text-white/35 hover:text-orange-500 dark:hover:text-brand-orange transition-colors",
|
|
2348
|
+
children: /* @__PURE__ */ jsx28(MessageCircle, { className: "w-5 h-5" })
|
|
2349
|
+
}
|
|
2350
|
+
)
|
|
2351
|
+
] }) })
|
|
2352
|
+
]
|
|
2353
|
+
}
|
|
2354
|
+
);
|
|
1774
2355
|
}
|
|
1775
2356
|
export {
|
|
1776
2357
|
AddressDisplay,
|
|
@@ -1783,6 +2364,7 @@ export {
|
|
|
1783
2364
|
DashboardLayout,
|
|
1784
2365
|
DataTable,
|
|
1785
2366
|
EmptyState,
|
|
2367
|
+
FeaturedProjectCard,
|
|
1786
2368
|
Field,
|
|
1787
2369
|
FormModal,
|
|
1788
2370
|
IconArrowRight,
|
|
@@ -1807,6 +2389,7 @@ export {
|
|
|
1807
2389
|
IconUndo,
|
|
1808
2390
|
IconX,
|
|
1809
2391
|
Input,
|
|
2392
|
+
InstalledProjectIcon,
|
|
1810
2393
|
JsonPanel,
|
|
1811
2394
|
JsonToggleButton,
|
|
1812
2395
|
MEDIA_LIMITS,
|
|
@@ -1815,6 +2398,7 @@ export {
|
|
|
1815
2398
|
MediaUploader,
|
|
1816
2399
|
MemoConditionsEditor,
|
|
1817
2400
|
PageShell,
|
|
2401
|
+
ProjectPagePreview,
|
|
1818
2402
|
SearchInput,
|
|
1819
2403
|
Section,
|
|
1820
2404
|
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.19",
|
|
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",
|