import { ArrowRight, BookOpen, Camera, ChevronDown, ChevronRight, Clock3, Coins, Compass, Crown, Gamepad2, GitFork, Heart, LogIn, MessageCircle, Palette, Pencil, ScanLine, Search, Settings, Share2, SlidersHorizontal, Sparkles, Star, ThumbsUp, Ticket, UserRound, } from 'lucide-react'; import { type ComponentType, type CSSProperties, type PointerEvent, type ReactNode, type RefObject, Suspense, useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import profileClockImage from '../../../media/profile/_Image (1).png'; import profileGamepadImage from '../../../media/profile/_Image (2).png'; import profileStillLifeImage from '../../../media/profile/_Image (3).png'; import profileCoinsImage from '../../../media/profile/_Image (4).png'; import profileGiftImage from '../../../media/profile/_Image (6).png'; import profileCommunityImage from '../../../media/profile/_Image (7).png'; import profileFeedbackImage from '../../../media/profile/_Image (8).png'; import profileMascotImage from '../../../media/profile/_Image (9).png'; import profilePointImage from '../../../media/profile/_Image.png'; import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth'; import type { CustomWorldLibraryEntry, PlatformBrowseHistoryEntry, ProfileDashboardCardKey, ProfileDashboardSummary, ProfilePlayedWorkSummary, ProfilePlayStatsResponse, ProfileSaveArchiveSummary, } from '../../../packages/shared/src/contracts/runtime'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import { isGeneratedLegacyPath, resolveAssetReadUrl, } from '../../services/assetReadUrlService'; import type { AuthUser } from '../../services/authService'; import { getPublicAuthUserByCode, getPublicAuthUserById, updateAuthProfile, } from '../../services/authService'; import { shouldShowRechargeEntry } from '../../services/payment/paymentPlatform'; import type { CustomWorldProfile } from '../../types'; import { useAuthUi } from '../auth/AuthUiContext'; import { CopyFeedbackButton } from '../common/CopyFeedbackButton'; import { LegalDocumentModal } from '../common/LegalDocumentModal'; import { getLegalDocument, type LegalDocumentId, } from '../common/legalDocuments'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; import { PlatformIconBadge } from '../common/PlatformIconBadge'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton'; import { PlatformPillBadge } from '../common/PlatformPillBadge'; import { PlatformStatusDialog } from '../common/PlatformStatusDialog'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformSubpanel } from '../common/PlatformSubpanel'; import { PlatformTextField } from '../common/PlatformTextField'; import { RUNTIME_RESOURCE_PENDING_SELECTOR } from '../common/RuntimeResourcePendingMarker'; import { SquareImageCropModal } from '../common/SquareImageCropModal'; import { UnifiedModal } from '../common/UnifiedModal'; import { buildCenteredSquareImageCropRect, clampSquareImageCropRect, type SquareImageCropRect, } from '../common/squareImageCropModel'; import { useCopyFeedback, } from '../common/useCopyFeedback'; import { CustomWorldCoverArtwork } from '../CustomWorldCoverArtwork'; import { canExposePublicWork, EDUTAINMENT_WORK_TAG, filterEdutainmentPublicWorks, filterGeneralPublicWorks, findPublicWorkForHistoryEntry, isEdutainmentEntryEnabled, } from '../platform-entry/platformEdutainmentVisibility'; import { ProfileLegalSection, ProfileSettingsRow, ProfileShortcutButton, ProfileStatCard, ProfileStatCardSkeleton, } from '../platform-entry/PlatformProfilePrimitives'; import { PlatformProfileModalShell } from '../platform-entry/PlatformProfileModalShell'; import { PlatformProfilePlayedWorksModal } from '../platform-entry/PlatformProfilePlayedWorksModal'; import { PlatformProfileQrScannerModal } from '../platform-entry/PlatformProfileQrScannerModal'; import { PlatformProfileRechargeModal } from '../platform-entry/PlatformProfileRechargeModal'; import { PlatformProfileReferralModal } from '../platform-entry/PlatformProfileReferralModal'; import { PlatformProfileRewardCodeRedeemModal } from '../platform-entry/PlatformProfileRewardCodeRedeemModal'; import { PlatformProfileTaskCenterModal } from '../platform-entry/PlatformProfileTaskCenterModal'; import { PlatformProfileWalletLedgerModal } from '../platform-entry/PlatformProfileWalletLedgerModal'; import { getInitialPlatformDesktopLayout } from '../platform-entry/platformEntryResponsive'; import { type RechargePaymentResult, usePlatformProfileCenterController, } from '../platform-entry/usePlatformProfileCenterController'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; import { RpgEntryBrandLogo } from './RpgEntryBrandLogo'; import { buildProfileDashboardPresentation, formatSnapshotTime, } from './rpgEntryProfileDashboardPresentation'; import { buildProfileTaskCardSummary } from './rpgEntryProfileTaskViewModel'; import { buildPlatformRankingEntries, buildPlatformRecommendFeedEntries, buildPublicCategoryGroups, buildPublicGalleryCardKey, dedupePlatformPublicGalleryEntries, DEFAULT_PLATFORM_CATEGORY_KIND_FILTER, DEFAULT_PLATFORM_CATEGORY_SORT_MODE, DEFAULT_PLATFORM_RANKING_TAB, filterPlatformWorkSearchResults, filterTodayPublishedEntries, getAllPlatformPublicEntries, getNextPlatformCategorySortMode, getPlatformCategoryKindFilterOption, getPlatformCategoryPrimaryMetric, getPlatformCategorySortOption, getPlatformPublicEntries, getPlatformRankingMetric, getPlatformRankingTabConfig, getPlatformSearchableWorkIds, getPlatformWorldLikeCount, getPlatformWorldPlayCount, getPlatformWorldRemixCount, isExactPublicWorkCodeSearch, matchesPlatformCategoryKindFilter, PLATFORM_CATEGORY_KIND_FILTERS, PLATFORM_CATEGORY_SORT_OPTIONS, PLATFORM_RANKING_TABS, type PlatformCategoryKindFilter, type PlatformCategorySortMode, type PlatformRankingMetric, type PlatformRankingTab, selectPlatformRecommendFeedWindow, sortPlatformCategoryEntries, } from './rpgEntryPublicGalleryViewModel'; import { buildRecommendSwipeRailClassName, clampRecommendDragOffset, hasRecommendDragStarted, RECOMMEND_ENTRY_COMMIT_ANIMATION_MS, type RecommendSwipeDirection, resolveRecommendCommitOffset, resolveRecommendDragCommitDirection, shouldAnimateRecommendSwipe, } from './rpgEntryRecommendSwipeDeckModel'; import { buildPlatformWorldDisplayTags, describePlatformPublicWorkKind, describePlatformThemeLabel, formatPlatformCompactCount, formatPlatformPublicAuthorAvatarLabel, formatPlatformWorkDisplayName, formatPlatformWorkDisplayTag, formatPlatformWorldTime, isBarkBattleGalleryEntry, isBigFishGalleryEntry, isEdutainmentGalleryEntry, isPuzzleGalleryEntry, isVisualNovelGalleryEntry, type PlatformPublicGalleryCard, type PlatformPublicWorkAuthorLookup, resolvePlatformPublicWorkAuthorLookup, resolvePlatformWorkAuthorDisplayName, resolvePlatformWorldCoverImage, resolvePlatformWorldCoverSlides, resolvePlatformWorldFallbackCoverImage, resolvePlatformWorldLeadPortrait, } from './rpgEntryWorldPresentation'; export type PlatformHomeTab = | 'home' | 'category' | 'create' | 'saves' | 'profile'; export interface RpgEntryHomeViewProps { activeTab: PlatformHomeTab; isDesktopLayout?: boolean; onTabChange: (tab: PlatformHomeTab) => void; hasSavedGame: boolean; savedSnapshot: HydratedSavedGameSnapshot | null; saveEntries: ProfileSaveArchiveSummary[]; saveError: string | null; featuredEntries: PlatformPublicGalleryCard[]; latestEntries: PlatformPublicGalleryCard[]; myEntries: CustomWorldLibraryEntry[]; historyEntries: PlatformBrowseHistoryEntry[]; profileDashboard: ProfileDashboardSummary | null; isLoadingPlatform: boolean; isLoadingDashboard: boolean; isResumingSaveWorldKey: string | null; platformError: string | null; dashboardError: string | null; onContinueGame: (snapshot?: HydratedSavedGameSnapshot | null) => void; onResumeSave: (entry: ProfileSaveArchiveSummary) => void; onOpenCreateWorld: () => void; onOpenCreateTypePicker: () => void; onOpenGalleryDetail: (entry: PlatformPublicGalleryCard) => void; onOpenChildMotionDemo?: () => void; onOpenBabyLoveDrawing?: () => void; onOpenRecommendGalleryDetail?: (entry: PlatformPublicGalleryCard) => void; recommendRuntimeContent?: ReactNode; activeRecommendEntryKey?: string | null; isStartingRecommendEntry?: boolean; isRecommendRuntimeReady?: boolean; recommendRuntimeError?: string | null; onSelectNextRecommendEntry?: (activeEntryKey?: string | null) => void; onSelectPreviousRecommendEntry?: (activeEntryKey?: string | null) => void; onLikeRecommendEntry?: (entry: PlatformPublicGalleryCard) => void; onShareRecommendEntry?: (entry: PlatformPublicGalleryCard) => void; onRemixRecommendEntry?: (entry: PlatformPublicGalleryCard) => void; onOpenLibraryDetail: ( entry: CustomWorldLibraryEntry, ) => void; onDeleteLibraryEntry?: ( entry: CustomWorldLibraryEntry, ) => void; deletingLibraryEntryId?: string | null; onSearchPublicCode?: (keyword: string) => void | Promise; isSearchingPublicCode?: boolean; onOpenProfileDashboardCard?: (cardKey: ProfileDashboardCardKey) => void; profilePlayStats?: ProfilePlayStatsResponse | null; isProfilePlayStatsOpen?: boolean; isProfilePlayStatsLoading?: boolean; profilePlayStatsError?: string | null; onCloseProfilePlayStats?: () => void; onOpenPlayedWork?: (work: ProfilePlayedWorkSummary) => void; onOpenFeedback?: () => void; onRechargeSuccess?: () => void | Promise; profileTaskRefreshKey?: number; createTabContent?: ReactNode; draftTabContent?: ReactNode; hasUnreadDraftUpdate?: boolean; } const PANEL_SURFACE_CLASS = 'platform-surface platform-surface--soft'; const HERO_SURFACE_CLASS = 'platform-surface platform-surface--hero platform-interactive-card min-w-0'; const MOBILE_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2'; const MOBILE_PROFILE_PAGE_STAGE_CLASS = 'platform-remap-surface min-w-0 space-y-4 pb-2'; const MOBILE_RECOMMEND_PAGE_STAGE_CLASS = 'platform-page-stage min-w-0 space-y-4 overflow-hidden pb-2'; const MOBILE_DISCOVER_PAGE_STAGE_CLASS = 'platform-remap-surface min-w-0 space-y-4 overflow-hidden pb-2'; const DESKTOP_PAGE_STAGE_CLASS = 'platform-page-stage platform-remap-surface min-w-0 space-y-5 pb-4'; const DESKTOP_DISCOVER_PAGE_STAGE_CLASS = 'platform-remap-surface min-w-0 space-y-5 pb-4'; const PLATFORM_HOME_TABS: PlatformHomeTab[] = [ 'home', 'category', 'create', 'saves', 'profile', ]; const AVATAR_MAX_FILE_SIZE = 5 * 1024 * 1024; const AVATAR_OUTPUT_SIZE = 256; const AVATAR_ALLOWED_TYPES = new Set(['image/jpeg', 'image/png', 'image/webp']); const PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS = 4200; const RECOMMEND_RUNTIME_COVER_MIN_VISIBLE_MS = 520; const RECOMMEND_RUNTIME_RESOURCE_IDLE_MS = 80; const RECOMMEND_RUNTIME_READY_FRAME_COUNT = 2; type RecommendResolvedCoverUrlMap = ReadonlyMap; function resolveRecommendDisplayCoverImage( imageSrc: string, fallbackSrc: string, resolvedCoverUrls?: RecommendResolvedCoverUrlMap, ) { const normalizedImageSrc = imageSrc.trim(); const normalizedFallbackSrc = fallbackSrc.trim(); if (!normalizedImageSrc) { return ( resolvedCoverUrls?.get(normalizedFallbackSrc) ?? normalizedFallbackSrc ); } const resolvedImageSrc = resolvedCoverUrls?.get(normalizedImageSrc); if (resolvedImageSrc) { return resolvedImageSrc; } if (isGeneratedLegacyPath(normalizedImageSrc)) { return ( resolvedCoverUrls?.get(normalizedFallbackSrc) ?? normalizedFallbackSrc ); } return normalizedImageSrc; } function resolveRecommendCardCoverImage(entry: PlatformPublicGalleryCard) { const cardCoverSlide = resolvePlatformWorldCoverSlides(entry)[0] ?? null; return ( cardCoverSlide?.imageSrc.trim() || resolvePlatformWorldCoverImage(entry) ); } function collectRecommendCoverPreloadUrls( entries: PlatformPublicGalleryCard[], ) { const urls = new Set(); entries.forEach((entry) => { resolvePlatformWorldCoverSlides(entry).forEach((slide) => { const slideImageSrc = slide.imageSrc.trim(); if (slideImageSrc) { urls.add(slideImageSrc); } }); [ resolveRecommendCardCoverImage(entry), resolvePlatformWorldCoverImage(entry), resolvePlatformWorldFallbackCoverImage(entry), ] .map((url) => url.trim()) .filter(Boolean) .forEach((url) => urls.add(url)); }); return [...urls]; } function useResolvedRecommendCoverImages( entries: PlatformPublicGalleryCard[], ): RecommendResolvedCoverUrlMap { const preloadUrls = useMemo( () => collectRecommendCoverPreloadUrls(entries), [entries], ); const preloadKey = preloadUrls.join('\n'); const [resolvedCoverUrls, setResolvedCoverUrls] = useState< Map >(() => new Map()); useEffect(() => { let cancelled = false; const cleanupCallbacks: Array<() => void> = []; const preloadCoverImage = ( imageSrc: string, onLoaded?: (loadedImageSrc: string) => void, ) => { if (!imageSrc || typeof Image === 'undefined') { onLoaded?.(imageSrc); return; } const image = new Image(); const cleanupImage = () => { image.onload = null; image.onerror = null; }; const finishImageLoad = () => { if (cancelled) { return; } cleanupImage(); onLoaded?.(imageSrc); }; const finishImageError = () => { if (cancelled) { return; } cleanupImage(); }; image.decoding = 'async'; image.onload = finishImageLoad; image.onerror = finishImageError; image.src = imageSrc; if (image.complete) { finishImageLoad(); } cleanupCallbacks.push(cleanupImage); }; setResolvedCoverUrls((currentUrls) => { const nextUrls = new Map(); preloadUrls.forEach((url) => { const cachedUrl = currentUrls.get(url); if (cachedUrl) { nextUrls.set(url, cachedUrl); return; } if (!isGeneratedLegacyPath(url)) { nextUrls.set(url, url); } }); return nextUrls; }); preloadUrls.forEach((url) => { if (!isGeneratedLegacyPath(url)) { preloadCoverImage(url); return; } void resolveAssetReadUrl(url) .then((resolvedUrl) => { if (cancelled || !resolvedUrl) { return; } preloadCoverImage(resolvedUrl, (loadedUrl) => { if (cancelled) { return; } setResolvedCoverUrls((currentUrls) => { if (currentUrls.get(url) === loadedUrl) { return currentUrls; } const nextUrls = new Map(currentUrls); nextUrls.set(url, loadedUrl); return nextUrls; }); }); }) .catch(() => undefined); }); return () => { cancelled = true; cleanupCallbacks.splice(0).forEach((cleanup) => cleanup()); }; }, [preloadKey, preloadUrls]); return resolvedCoverUrls; } function scheduleRecommendRuntimeReady( signal: AbortSignal, onReady: () => void, ) { if (signal.aborted) { return null; } let animationFrameId: number | null = null; let remainingFrameCount = RECOMMEND_RUNTIME_READY_FRAME_COUNT; const tick = () => { animationFrameId = null; if (signal.aborted) { return; } remainingFrameCount -= 1; if (remainingFrameCount <= 0) { onReady(); return; } animationFrameId = window.requestAnimationFrame(tick); }; animationFrameId = window.requestAnimationFrame(tick); return () => { if (animationFrameId !== null) { window.cancelAnimationFrame(animationFrameId); } }; } function getRecommendRuntimeImageSource(image: HTMLImageElement) { return ( image.currentSrc || image.getAttribute('src') || image.getAttribute('srcset') || '' ).trim(); } function getRecommendRuntimeMediaSource(media: HTMLMediaElement) { return (media.currentSrc || media.getAttribute('src') || '').trim(); } function collectRecommendRuntimeBackgroundUrls(root: HTMLElement) { const urls = new Set(); root.querySelectorAll('[style]').forEach((element) => { const backgroundImage = element.style.backgroundImage; if (!backgroundImage || backgroundImage === 'none') { return; } const pattern = /url\((?:"([^"]*)"|'([^']*)'|([^)]*))\)/giu; let match: RegExpExecArray | null; while ((match = pattern.exec(backgroundImage))) { const url = (match[1] ?? match[2] ?? match[3] ?? '').trim(); if (url) { urls.add(url); } } }); return urls; } function readyRecommendRuntime( root: HTMLElement | null, signal: AbortSignal, ): Promise { if (!root || signal.aborted) { return Promise.resolve(false); } const runtimeRoot = root; return new Promise((resolve) => { let scanFrameId: number | null = null; let readyIdleTimeoutId: number | null = null; let pendingRecheckTimeoutId: number | null = null; let readyFrameCleanup: (() => void) | null = null; let settled = false; const cleanupCallbacks: Array<() => void> = []; const pendingImageListeners = new Map< HTMLImageElement, { src: string; cleanup: () => void } >(); const pendingMediaListeners = new Map< HTMLMediaElement, { src: string; cleanup: () => void } >(); const settledImageSources = new WeakMap(); const settledMediaSources = new WeakMap(); const loadedBackgroundUrls = new Set(); const pendingBackgroundPreloads = new Map void>(); const cancelReadySchedule = () => { if (readyIdleTimeoutId !== null) { window.clearTimeout(readyIdleTimeoutId); readyIdleTimeoutId = null; } if (readyFrameCleanup) { readyFrameCleanup(); readyFrameCleanup = null; } }; const cancelPendingRecheck = () => { if (pendingRecheckTimeoutId !== null) { window.clearTimeout(pendingRecheckTimeoutId); pendingRecheckTimeoutId = null; } }; const finish = (value: boolean) => { if (settled) { return; } settled = true; cancelReadySchedule(); cancelPendingRecheck(); cleanupCallbacks.splice(0).forEach((cleanup) => cleanup()); if (scanFrameId !== null) { window.cancelAnimationFrame(scanFrameId); } resolve(value); }; const abort = () => finish(false); signal.addEventListener('abort', abort, { once: true }); cleanupCallbacks.push(() => signal.removeEventListener('abort', abort)); cleanupCallbacks.push(() => { pendingImageListeners.forEach(({ cleanup }) => cleanup()); pendingImageListeners.clear(); pendingMediaListeners.forEach(({ cleanup }) => cleanup()); pendingMediaListeners.clear(); pendingBackgroundPreloads.forEach((cleanup) => cleanup()); pendingBackgroundPreloads.clear(); }); const scheduleScan = () => { if (settled) { return; } cancelReadySchedule(); cancelPendingRecheck(); if (scanFrameId !== null) { return; } scanFrameId = window.requestAnimationFrame(() => { scanFrameId = null; scanResources(); }); }; const scheduleReady = () => { cancelReadySchedule(); readyIdleTimeoutId = window.setTimeout(() => { readyIdleTimeoutId = null; readyFrameCleanup = scheduleRecommendRuntimeReady(signal, () => finish(true), ); if (readyFrameCleanup === null) { finish(false); } }, RECOMMEND_RUNTIME_RESOURCE_IDLE_MS); }; const preloadBackgroundUrl = (url: string) => { if (loadedBackgroundUrls.has(url) || pendingBackgroundPreloads.has(url)) { return; } if (typeof Image === 'undefined') { loadedBackgroundUrls.add(url); return; } const image = new Image(); const cleanup = () => { image.onload = null; image.onerror = null; }; const markReady = () => { cleanup(); pendingBackgroundPreloads.delete(url); loadedBackgroundUrls.add(url); scheduleScan(); }; image.decoding = 'async'; image.onload = markReady; image.onerror = markReady; pendingBackgroundPreloads.set(url, cleanup); image.src = url; if (image.complete) { markReady(); } }; function scanResources() { if (signal.aborted) { finish(false); return; } const currentImages = new Set( Array.from(runtimeRoot.querySelectorAll('img')), ); const currentMedia = new Set( Array.from( runtimeRoot.querySelectorAll('audio,video'), ), ); pendingImageListeners.forEach((entry, image) => { const currentSrc = getRecommendRuntimeImageSource(image); if ( !currentImages.has(image) || currentSrc !== entry.src || !currentSrc ) { entry.cleanup(); pendingImageListeners.delete(image); } }); pendingMediaListeners.forEach((entry, media) => { const currentSrc = getRecommendRuntimeMediaSource(media); if ( !currentMedia.has(media) || currentSrc !== entry.src || !currentSrc ) { entry.cleanup(); pendingMediaListeners.delete(media); } }); currentImages.forEach((image) => { const imageSrc = getRecommendRuntimeImageSource(image); const settledImageSrc = settledImageSources.get(image); if (settledImageSrc && settledImageSrc !== imageSrc) { settledImageSources.delete(image); } if (!imageSrc || image.complete) { if (imageSrc && image.complete) { settledImageSources.set(image, imageSrc); } return; } if (settledImageSources.get(image) === imageSrc) { return; } const existingEntry = pendingImageListeners.get(image); if (existingEntry?.src === imageSrc) { return; } existingEntry?.cleanup(); const markImageReady = () => { const activeEntry = pendingImageListeners.get(image); if (activeEntry?.src !== imageSrc) { return; } settledImageSources.set(image, imageSrc); activeEntry.cleanup(); pendingImageListeners.delete(image); scheduleScan(); }; const cleanupImageListeners = () => { image.removeEventListener('load', markImageReady); image.removeEventListener('error', markImageReady); }; image.addEventListener('load', markImageReady, { once: true }); image.addEventListener('error', markImageReady, { once: true }); pendingImageListeners.set(image, { src: imageSrc, cleanup: cleanupImageListeners, }); if (image.complete) { markImageReady(); } }); currentMedia.forEach((media) => { const mediaSrc = getRecommendRuntimeMediaSource(media); const settledMediaSrc = settledMediaSources.get(media); const mediaReadyThreshold = typeof HTMLMediaElement !== 'undefined' ? HTMLMediaElement.HAVE_CURRENT_DATA : 2; if (settledMediaSrc && settledMediaSrc !== mediaSrc) { settledMediaSources.delete(media); } if ( !mediaSrc || media.readyState >= mediaReadyThreshold || media.error ) { if (mediaSrc && media.readyState >= mediaReadyThreshold) { settledMediaSources.set(media, mediaSrc); } return; } if (settledMediaSources.get(media) === mediaSrc) { return; } const existingEntry = pendingMediaListeners.get(media); if (existingEntry?.src === mediaSrc) { return; } existingEntry?.cleanup(); const markMediaReady = () => { const activeEntry = pendingMediaListeners.get(media); if (activeEntry?.src !== mediaSrc) { return; } settledMediaSources.set(media, mediaSrc); activeEntry.cleanup(); pendingMediaListeners.delete(media); scheduleScan(); }; const cleanupMediaListeners = () => { media.removeEventListener('loadeddata', markMediaReady); media.removeEventListener('canplaythrough', markMediaReady); media.removeEventListener('error', markMediaReady); }; media.addEventListener('loadeddata', markMediaReady, { once: true }); media.addEventListener('canplaythrough', markMediaReady, { once: true, }); media.addEventListener('error', markMediaReady, { once: true }); pendingMediaListeners.set(media, { src: mediaSrc, cleanup: cleanupMediaListeners, }); if (media.readyState >= mediaReadyThreshold || media.error) { markMediaReady(); } }); const currentBackgroundUrls = collectRecommendRuntimeBackgroundUrls(runtimeRoot); pendingBackgroundPreloads.forEach((cleanup, url) => { if (!currentBackgroundUrls.has(url)) { cleanup(); pendingBackgroundPreloads.delete(url); } }); currentBackgroundUrls.forEach((url) => preloadBackgroundUrl(url)); const hasPendingResourceMarker = Boolean( runtimeRoot.querySelector(RUNTIME_RESOURCE_PENDING_SELECTOR), ); if ( hasPendingResourceMarker || pendingImageListeners.size > 0 || pendingMediaListeners.size > 0 || pendingBackgroundPreloads.size > 0 ) { cancelReadySchedule(); if (pendingRecheckTimeoutId === null) { pendingRecheckTimeoutId = window.setTimeout(() => { pendingRecheckTimeoutId = null; scheduleScan(); }, RECOMMEND_RUNTIME_RESOURCE_IDLE_MS); } return; } cancelPendingRecheck(); scheduleReady(); } if (typeof MutationObserver !== 'undefined') { const observer = new MutationObserver(scheduleScan); observer.observe(runtimeRoot, { childList: true, subtree: true, attributes: true, attributeFilter: [ 'src', 'srcset', 'style', 'data-runtime-resource-pending', ], }); cleanupCallbacks.push(() => observer.disconnect()); } scanResources(); }); } type DiscoverChannel = | 'recommend' | 'today' | 'category' | 'ranking' | 'edutainment'; const DISCOVER_CHANNELS: Array<{ id: DiscoverChannel; label: string; }> = [ { id: 'recommend', label: '推荐' }, { id: 'today', label: '今日' }, { id: 'category', label: '分类' }, { id: 'ranking', label: '排行' }, ]; const EDUTAINMENT_DISCOVER_CHANNEL = { id: 'edutainment', label: EDUTAINMENT_WORK_TAG, } as const; const BABY_LOVE_DRAWING_DEFAULT_CARD = { title: '宝贝爱画', subtitle: '空白画板', summary: '挥动小手画一张画。', }; const CHILD_MOTION_DEMO_DEFAULT_CARD = { title: '热身关卡', subtitle: '动作识别热身', summary: '站位、招手和左右手活动。', }; function ResolvedAssetBackdrop({ src, fallbackSrc, alt, className, ariaHidden = false, }: { src?: string | null; fallbackSrc?: string | null; alt: string; className: string; ariaHidden?: boolean; }) { return ( ); } function PlatformWorkCoverArtwork({ entry, imageSrc, fallbackSrc, alt, className, }: { entry: PlatformPublicGalleryCard; imageSrc?: string | null; fallbackSrc?: string | null; alt: string; className: string; }) { if (isBarkBattleGalleryEntry(entry)) { return ( ); } return ( ); } function SectionHeader({ title, detail }: { title: string; detail: string }) { return (
{detail}
{title}
); } function PublicCodeSearchBar({ value, onChange, onSubmit, isSearching, className, }: { value: string; onChange: (value: string) => void; onSubmit: () => void; isSearching: boolean; className?: string; }) { return (
onChange(event.target.value)} onKeyDown={(event) => { if (event.key === 'Enter') { event.preventDefault(); onSubmit(); } }} placeholder="搜索作品号、名称、作者、描述" className="w-full min-w-0 bg-transparent text-sm text-[var(--platform-text-strong)] outline-none placeholder:text-[var(--platform-text-soft)]" />
); } function TopbarWalletShortcutButton({ variant, balanceLabel, onClick, }: { variant: 'mobile' | 'desktop'; balanceLabel: string; onClick: () => void; }) { const isMobile = variant === 'mobile'; return ( } size="xs" tone="neutral" className={ isMobile ? '!h-6 !w-6 !bg-[#ffe0ab] !text-[#cf7b34]' : '!bg-[#ffe0ab] !text-[#cf7b34]' } /> {balanceLabel} ); } function WorldCard({ entry, onClick, className, authorAvatarUrl, authorSummary, feedCardKey, enableCoverCarousel = false, isCoverCarouselActive = false, variant = 'standard', }: { entry: PlatformPublicGalleryCard; onClick: () => void; className?: string; authorAvatarUrl?: string | null; authorSummary?: PublicUserSummary | null; feedCardKey?: string; enableCoverCarousel?: boolean; isCoverCarouselActive?: boolean; variant?: 'standard' | 'immersive'; }) { const fallbackCoverImage = resolvePlatformWorldCoverImage(entry); const fallbackAssetCoverImage = resolvePlatformWorldFallbackCoverImage(entry); const coverSlides = useMemo(() => { if (!enableCoverCarousel) { return fallbackCoverImage ? [ { id: 'cover', imageSrc: fallbackCoverImage, label: entry.worldName, }, ] : []; } return resolvePlatformWorldCoverSlides(entry); }, [enableCoverCarousel, entry, fallbackCoverImage]); const [activeCoverIndex, setActiveCoverIndex] = useState(0); const visibleCoverIndex = isCoverCarouselActive ? activeCoverIndex : 0; const activeCoverSlide = coverSlides[visibleCoverIndex] ?? coverSlides[0] ?? null; const coverImage = activeCoverSlide?.imageSrc ?? ''; const displayName = formatPlatformWorkDisplayName(entry.worldName); const tags = buildPlatformWorldDisplayTags(entry, 3); const playCount = getPlatformWorldPlayCount(entry); const remixCount = getPlatformWorldRemixCount(entry); const likeCount = getPlatformWorldLikeCount(entry); const typeLabel = describePlatformPublicWorkKind(entry); const authorName = resolvePlatformWorkAuthorDisplayName(entry, authorSummary); const authorAvatarLabel = formatPlatformPublicAuthorAvatarLabel(authorName); const normalizedAuthorAvatarUrl = authorAvatarUrl?.trim() ?? ''; const cardLabel = `${entry.worldName},${typeLabel},${formatPlatformCompactCount(playCount)}游玩,${formatPlatformCompactCount(remixCount)}改造,${formatPlatformCompactCount(likeCount)}点赞`; const coverStats = [ { label: '游玩', value: playCount, icon: Gamepad2, }, { label: '改造', value: remixCount, icon: Pencil, }, { label: '点赞', value: likeCount, icon: Heart, }, ]; useEffect(() => { setActiveCoverIndex(0); }, [entry.ownerUserId, entry.profileId, coverSlides.length]); useEffect(() => { if (!isCoverCarouselActive) { setActiveCoverIndex(0); } }, [isCoverCarouselActive]); useEffect(() => { if (!isCoverCarouselActive || coverSlides.length <= 1) { return undefined; } const timerId = window.setInterval(() => { setActiveCoverIndex((current) => (current + 1) % coverSlides.length); }, PLATFORM_WORK_COVER_CAROUSEL_INTERVAL_MS); return () => { window.clearInterval(timerId); }; }, [coverSlides.length, isCoverCarouselActive]); return ( ); } function CreationLibraryCard({ entry, onClick, onDelete, isDeleting = false, }: { entry: CustomWorldLibraryEntry; onClick: () => void; onDelete?: () => void; isDeleting?: boolean; }) { const coverImage = resolvePlatformWorldCoverImage(entry); const leadPortrait = resolvePlatformWorldLeadPortrait(entry); const statusLabel = entry.visibility === 'published' ? '已发布' : '草稿'; const metaLabel = entry.visibility === 'published' ? formatPlatformWorldTime(entry.publishedAt) : '仅自己可见'; const primaryTag = buildPlatformWorldDisplayTags(entry, 1)[0] ?? formatPlatformWorkDisplayTag(describePlatformThemeLabel(entry.themeMode)); const displayName = formatPlatformWorkDisplayName(entry.worldName); const summaryText = entry.summaryText || entry.subtitle || '继续补完这个世界的设定与游玩入口。'; return ( ); } function RecommendRuntimePreviewCard({ entry, position, resolvedCoverUrls, }: { entry: PlatformPublicGalleryCard; position?: 'previous' | 'next' | 'cover'; resolvedCoverUrls?: RecommendResolvedCoverUrlMap; }) { const rawCoverImage = resolveRecommendCardCoverImage(entry); const rawFallbackCoverImage = resolvePlatformWorldFallbackCoverImage(entry); const resolvedCoverImage = resolveRecommendDisplayCoverImage( rawCoverImage, rawFallbackCoverImage, resolvedCoverUrls, ); const fallbackCoverImage = resolvedCoverUrls?.get(rawFallbackCoverImage) ?? rawFallbackCoverImage; const previewKey = `${buildPublicGalleryCardKey(entry)}:${position ?? 'preview'}`; const shouldLockCoverImage = position === 'cover'; const [lockedCoverImage, setLockedCoverImage] = useState({ key: previewKey, imageSrc: resolvedCoverImage, fallbackSrc: fallbackCoverImage, }); useEffect(() => { setLockedCoverImage((currentValue) => { if (shouldLockCoverImage) { return currentValue; } return { key: previewKey, imageSrc: resolvedCoverImage, fallbackSrc: fallbackCoverImage, }; }); }, [ fallbackCoverImage, previewKey, resolvedCoverImage, shouldLockCoverImage, ]); const coverImage = shouldLockCoverImage ? lockedCoverImage.imageSrc : resolvedCoverImage; const displayFallbackCoverImage = shouldLockCoverImage ? lockedCoverImage.imageSrc : fallbackCoverImage; const displayName = formatPlatformWorkDisplayName(entry.worldName); const typeLabel = describePlatformPublicWorkKind(entry); const previewClassName = `platform-recommend-runtime-preview ${ position === 'cover' ? 'platform-recommend-runtime-preview--cover' : '' }`; return (