Merge branch 'codex/profile-mobile-ui-reference'

This commit is contained in:
2026-05-25 01:41:05 +08:00
74 changed files with 5512 additions and 1090 deletions

View File

@@ -11,7 +11,7 @@ import {
Coins,
Compass,
Copy,
FileText,
Crown,
Gamepad2,
GitFork,
Heart,
@@ -20,9 +20,11 @@ import {
Palette,
Pencil,
Plus,
ScanLine,
Search,
Settings,
Share2,
ShieldCheck,
SlidersHorizontal,
Sparkles,
Star,
@@ -45,6 +47,16 @@ import {
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 profileInviteImage from '../../../media/profile/_Image (5).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 communityQqQrImage from '../../../media/social-media-group/qq.png';
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
@@ -215,8 +227,12 @@ const MOBILE_PAGE_STAGE_CLASS =
'platform-page-stage platform-remap-surface min-w-0 space-y-4 overflow-hidden 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 DESKTOP_LAYOUT_QUERY = '(min-width: 1024px)';
const PLATFORM_HOME_TABS: PlatformHomeTab[] = [
'home',
@@ -2384,12 +2400,14 @@ function ProfileStatCard({
value,
onClick,
icon,
imageSrc,
}: {
cardKey: ProfileDashboardCardKey;
label: string;
value: string;
onClick?: ((cardKey: ProfileDashboardCardKey) => void) | null;
icon: ComponentType<{ className?: string }>;
imageSrc?: string;
}) {
const Icon = icon;
@@ -2397,16 +2415,23 @@ function ProfileStatCard({
<button
type="button"
onClick={onClick ? () => onClick(cardKey) : undefined}
className="platform-subpanel flex min-h-[5.75rem] flex-col items-center justify-center rounded-[1.35rem] px-3 py-3 text-center transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
aria-label={`${label} ${value}`}
className="platform-profile-stat-card flex min-h-[5.75rem] items-center justify-center gap-2 px-3 py-3 text-center transition"
>
<div className="flex w-full items-center justify-center gap-2 text-[var(--platform-text-soft)]">
<Icon className="h-4 w-4" />
<span className="whitespace-nowrap text-[11px] tracking-[0.16em]">
{label}
</span>
<div className="platform-profile-stat-card__icon">
{imageSrc ? (
<img src={imageSrc} alt="" className="h-full w-full object-contain" />
) : (
<Icon className="h-5 w-5" />
)}
</div>
<div className="mt-2 whitespace-nowrap text-lg font-black leading-none text-[var(--platform-text-strong)]">
{value}
<div className="min-w-0 text-left">
<div className="platform-profile-stat-card__value whitespace-nowrap text-lg font-black leading-none text-[var(--platform-text-strong)]">
{value}
</div>
<div className="platform-profile-stat-card__label mt-1 whitespace-nowrap text-[12px] font-medium text-[var(--platform-text-soft)]">
{label}
</div>
</div>
</button>
);
@@ -2426,11 +2451,13 @@ function ProfileShortcutButton({
subLabel,
icon,
onClick,
imageSrc,
}: {
label: string;
subLabel?: ReactNode;
icon: ComponentType<{ className?: string }>;
onClick?: (() => void) | null;
imageSrc?: string;
}) {
const Icon = icon;
@@ -2438,16 +2465,20 @@ function ProfileShortcutButton({
<button
type="button"
onClick={onClick ?? undefined}
className="platform-subpanel flex min-h-[5.25rem] flex-col items-center justify-center gap-2 rounded-[1.2rem] px-3 py-3 text-center transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
className="platform-profile-shortcut-button flex min-h-[5.25rem] flex-col items-center justify-center gap-2 px-2.5 py-3 text-center transition"
>
<div className="platform-profile-chip flex h-10 w-10 items-center justify-center rounded-full">
<Icon className="h-[1.125rem] w-[1.125rem]" />
<div className="platform-profile-shortcut-button__icon">
{imageSrc ? (
<img src={imageSrc} alt="" className="h-full w-full object-contain" />
) : (
<Icon className="h-[1.125rem] w-[1.125rem]" />
)}
</div>
<div className="text-sm font-semibold text-[var(--platform-text-strong)]">
<div className="platform-profile-shortcut-button__label whitespace-nowrap text-[13px] font-semibold text-[var(--platform-text-strong)]">
{label}
</div>
{subLabel ? (
<div className="flex min-h-4 items-center justify-center gap-1 text-[11px] font-semibold text-[var(--platform-text-soft)]">
<div className="platform-profile-shortcut-button__sub-label flex min-h-4 items-center justify-center gap-1 whitespace-nowrap text-[11px] font-medium text-[var(--platform-text-soft)]">
{subLabel}
</div>
) : null}
@@ -2455,6 +2486,72 @@ function ProfileShortcutButton({
);
}
function ProfileSettingsRow({
label,
icon,
onClick,
}: {
label: string;
icon: ComponentType<{ className?: string }>;
onClick: () => void;
}) {
const Icon = icon;
return (
<button
type="button"
onClick={onClick}
className="platform-profile-settings-row flex w-full items-center justify-between gap-3 px-4 py-4 text-left transition"
>
<span className="flex min-w-0 items-center gap-3">
<span className="platform-profile-settings-row__icon">
<Icon className="h-5 w-5" />
</span>
<span className="truncate text-[15px] font-semibold text-[var(--platform-text-strong)]">
{label}
</span>
</span>
<ChevronRight className="h-4 w-4 shrink-0 text-[var(--platform-text-soft)]" />
</button>
);
}
function ProfileSecondaryShortcutButton({
label,
subLabel,
icon,
onClick,
}: {
label: string;
subLabel?: string;
icon: ComponentType<{ className?: string }>;
onClick: () => void;
}) {
const Icon = icon;
return (
<button
type="button"
onClick={onClick}
className="platform-profile-secondary-shortcut inline-flex items-center gap-2 rounded-full px-3 py-2 text-left"
>
<span className="platform-profile-secondary-shortcut__icon">
<Icon className="h-4 w-4" />
</span>
<span className="min-w-0">
<span className="block truncate text-[13px] font-semibold text-[var(--platform-text-strong)]">
{label}
</span>
{subLabel ? (
<span className="mt-0.5 block truncate text-[11px] font-medium text-[var(--platform-text-soft)]">
{subLabel}
</span>
) : null}
</span>
</button>
);
}
function ProfileLegalSection({
onOpenDocument,
}: {
@@ -2462,33 +2559,21 @@ function ProfileLegalSection({
}) {
return (
<section
className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}
className="platform-profile-legal-strip"
aria-label="法律信息"
>
<div className="mb-3 text-sm font-black text-[var(--platform-text-strong)]">
</div>
<div className="platform-subpanel overflow-hidden rounded-[1.25rem]">
<div className="platform-profile-legal-strip__links">
{LEGAL_DOCUMENTS.map((document, index) => (
<button
key={document.id}
type="button"
onClick={() => onOpenDocument(document.id)}
className={`flex w-full items-center justify-between gap-3 px-4 py-3 text-left transition hover:bg-[var(--platform-button-secondary-fill)] ${
index > 0
? 'border-t border-[var(--platform-subpanel-border)]'
: ''
}`}
className="platform-profile-legal-strip__link"
>
<span className="flex min-w-0 items-center gap-3">
<span className="platform-profile-chip flex h-8 w-8 shrink-0 items-center justify-center rounded-full">
<FileText className="h-4 w-4" />
</span>
<span className="truncate text-sm font-semibold text-[var(--platform-text-strong)]">
{document.title}
</span>
</span>
<ChevronRight className="h-4 w-4 shrink-0 text-[var(--platform-text-soft)]" />
{document.title}
{index < LEGAL_DOCUMENTS.length - 1 ? (
<span aria-hidden="true" className="platform-profile-legal-strip__divider" />
) : null}
</button>
))}
</div>
@@ -2496,7 +2581,7 @@ function ProfileLegalSection({
href={ICP_RECORD_URL}
target="_blank"
rel="noreferrer"
className="mt-3 block text-center text-xs font-semibold text-[var(--platform-text-soft)] transition hover:text-[var(--platform-cool-text)]"
className="platform-profile-legal-strip__record"
>
{ICP_RECORD_NUMBER}
</a>
@@ -5369,7 +5454,7 @@ export function RpgEntryHomeView({
);
const mobileDiscoverContent: ReactNode = (
<div className={`${MOBILE_PAGE_STAGE_CLASS} platform-mobile-home-stage`}>
<div className={`${MOBILE_DISCOVER_PAGE_STAGE_CLASS} platform-mobile-home-stage`}>
<PublicCodeSearchBar
value={mobileSearchKeyword}
onChange={updateMobileSearchKeyword}
@@ -5584,7 +5669,7 @@ export function RpgEntryHomeView({
);
const desktopDiscoverContent: ReactNode = (
<div className={DESKTOP_PAGE_STAGE_CLASS}>
<div className={DESKTOP_DISCOVER_PAGE_STAGE_CLASS}>
<div className="platform-mobile-home-channelbar flex min-w-0 gap-4 overflow-x-auto pb-1 scrollbar-hide">
{visibleDiscoverChannels.map((channel) => {
const active = discoverChannel === channel.id;
@@ -5839,30 +5924,55 @@ export function RpgEntryHomeView({
const savesContent: ReactNode = draftTabContent ?? fallbackDraftContent;
const profileContent: ReactNode = (
<div className={MOBILE_PAGE_STAGE_CLASS}>
<div className={`${MOBILE_PAGE_STAGE_CLASS} platform-profile-page`}>
{authUi?.user ? (
<>
<section className="platform-profile-hero rounded-[1.8rem] p-4">
<div className="flex items-start justify-between gap-3">
<div className="flex min-w-0 items-center gap-3">
<section className="platform-profile-header">
<div className="platform-profile-header__actions">
<button
type="button"
onClick={openRechargeOrRewardCodeModal}
className="platform-profile-header__icon-button"
aria-label="打开充值入口"
>
<ScanLine className="h-5 w-5" />
</button>
<button
type="button"
onClick={() => authUi.openSettingsModal()}
className="platform-profile-header__icon-button"
aria-label="打开设置"
>
<Settings className="h-5 w-5" />
</button>
</div>
<img
src={profileStillLifeImage}
alt=""
className="platform-profile-scene-decor"
/>
<div className="platform-profile-header__identity">
<div className="platform-profile-header__identity-row flex min-w-0 items-center gap-4">
<button
type="button"
onClick={openAvatarPicker}
className="platform-profile-avatar relative h-16 w-16 shrink-0 rounded-[1.4rem]"
className="platform-profile-avatar relative h-[5.15rem] w-[5.15rem] shrink-0 rounded-full"
aria-label="上传头像"
>
{avatarUrl ? (
<img
src={avatarUrl}
alt=""
className="h-full w-full rounded-[1.4rem] object-cover"
className="h-full w-full rounded-full object-cover"
/>
) : (
<span className="flex h-full w-full items-center justify-center text-2xl font-black">
{avatarLabel}
</span>
<img
src={profileMascotImage}
alt=""
className="h-full w-full rounded-full object-cover"
/>
)}
<span className="platform-profile-camera absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full">
<span className="platform-profile-camera absolute bottom-0 right-0 flex h-7 w-7 items-center justify-center rounded-full">
<Camera className="h-3.5 w-3.5" />
</span>
</button>
@@ -5877,28 +5987,27 @@ export function RpgEntryHomeView({
}
/>
<div className="min-w-0">
<div className="platform-profile-header__text min-w-0">
<div className="flex items-center gap-2">
<div className="truncate text-xl font-black text-[var(--platform-text-strong)]">
<div className="platform-profile-header__name truncate text-[20px] font-black leading-tight text-[var(--platform-text-strong)]">
{authUi.user.displayName}
</div>
<button
type="button"
onClick={openNicknameModal}
className="platform-profile-icon-button flex h-7 w-7 items-center justify-center rounded-full"
className="platform-profile-edit-button"
aria-label="修改昵称"
>
<Pencil className="h-3.5 w-3.5" />
<Pencil className="h-5 w-5" />
</button>
</div>
<div className="mt-1 flex flex-wrap items-center gap-2 text-xs text-[var(--platform-text-soft)]">
<span> {publicUserCode}</span>
<div className="platform-profile-header__code mt-3 flex flex-wrap items-center gap-2 text-[13px] text-[var(--platform-text-base)]">
<span> {publicUserCode}</span>
<button
type="button"
onClick={copyProfilePublicUserCode}
className="platform-profile-chip flex items-center gap-1 rounded-full px-2 py-1"
className="platform-profile-copy-button"
>
<Copy className="h-3 w-3" />
{profileCopyState === 'copied'
? '已复制'
: profileCopyState === 'failed'
@@ -5908,32 +6017,33 @@ export function RpgEntryHomeView({
</div>
</div>
</div>
<button
type="button"
onClick={openRechargeOrRewardCodeModal}
className="platform-profile-action flex shrink-0 items-center gap-2 rounded-[1.1rem] px-3 py-2 text-left"
>
{showRechargeEntry ? (
<Coins className="h-4 w-4" />
) : (
<Ticket className="h-4 w-4" />
)}
<div>
<div className="text-xs font-bold">
{showRechargeEntry ? '充值' : '兑换码'}
</div>
<div className="text-[10px] opacity-80">
{showRechargeEntry ? '泥点/会员' : '福利奖励'}
</div>
</div>
<ChevronRight className="h-4 w-4 opacity-80" />
</button>
</div>
</section>
<section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
<div className="grid grid-cols-3 gap-3">
<button
type="button"
onClick={openRechargeOrRewardCodeModal}
className="platform-profile-membership-card"
aria-label="查看权益"
>
<span className="platform-profile-membership-card__badge">
<Crown className="platform-profile-membership-card__crown" />
</span>
<span className="min-w-0 flex-1">
<span className="platform-profile-membership-card__title block text-[18px] font-black leading-tight text-white">
</span>
<span className="platform-profile-membership-card__subtitle mt-2 block text-[13px] font-medium text-white/92">
</span>
</span>
<span className="platform-profile-membership-card__action">
</span>
</button>
<section className="platform-profile-stats-panel" aria-label="我的数据">
<div className="platform-profile-stats-grid grid grid-cols-3 gap-3">
{isLoadingDashboard ? (
<>
<ProfileStatCardSkeleton />
@@ -5944,23 +6054,26 @@ export function RpgEntryHomeView({
<>
<ProfileStatCard
cardKey="wallet"
label="泥点"
label="泥点余额"
value="暂不可用"
icon={Coins}
imageSrc={profilePointImage}
onClick={openWalletLedgerPanel}
/>
<ProfileStatCard
cardKey="playTime"
label="游戏时长"
label="累计游戏时长"
value="暂不可用"
icon={Clock3}
imageSrc={profileClockImage}
onClick={onOpenProfileDashboardCard}
/>
<ProfileStatCard
cardKey="playedWorks"
label="玩过"
label="已玩游戏数量"
value="暂不可用"
icon={BookOpen}
imageSrc={profileGamepadImage}
onClick={onOpenProfileDashboardCard}
/>
</>
@@ -5968,23 +6081,26 @@ export function RpgEntryHomeView({
<>
<ProfileStatCard
cardKey="wallet"
label="泥点"
label="泥点余额"
value={formatDashboardCount(remainingNarrativeCoins)}
icon={Coins}
imageSrc={profilePointImage}
onClick={openWalletLedgerPanel}
/>
<ProfileStatCard
cardKey="playTime"
label="游戏时长"
label="累计游戏时长"
value={totalPlayTime}
icon={Clock3}
imageSrc={profileClockImage}
onClick={onOpenProfileDashboardCard}
/>
<ProfileStatCard
cardKey="playedWorks"
label="玩过"
label="已玩游戏数量"
value={`${formatDashboardCount(playedWorkCount)}`}
icon={BookOpen}
imageSrc={profileGamepadImage}
onClick={onOpenProfileDashboardCard}
/>
</>
@@ -5992,101 +6108,125 @@ export function RpgEntryHomeView({
</div>
</section>
<button
type="button"
onClick={openTaskCenterPanel}
className="platform-profile-daily-task-card"
>
<span className="min-w-0 flex-1">
<span className="platform-profile-daily-task-card__title block text-[15px] font-black text-[var(--platform-text-strong)]">
</span>
<span className="platform-profile-daily-task-card__desc mt-4 block text-[13px] font-medium text-[var(--platform-text-base)]">
<span className="text-[#c45b2a]">10</span>
</span>
<span className="platform-profile-daily-task-card__progress mt-4 flex items-center gap-3">
<span className="platform-profile-daily-task-card__progress-value text-[14px] font-semibold text-[#dc3f0e]">
0 / 1
</span>
<span className="platform-profile-daily-task-card__track">
<span className="platform-profile-daily-task-card__bar" />
</span>
</span>
</span>
<img
src={profileMascotImage}
alt=""
className="platform-profile-daily-task-card__mascot"
/>
<span className="platform-profile-daily-task-card__action">
</span>
</button>
<section
className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}
className="platform-profile-shortcut-panel"
aria-label="常用功能"
>
<div className="grid grid-cols-3 gap-3">
<div className="platform-profile-shortcut-grid">
<ProfileShortcutButton
label="每日任务"
subLabel={
<>
<span>10</span>
<Coins className="h-3 w-3" />
</>
}
icon={Star}
onClick={openTaskCenterPanel}
/>
<ProfileShortcutButton
label={showRechargeEntry ? '充值' : '兑换码'}
subLabel={showRechargeEntry ? '泥点/会员' : '福利奖励'}
icon={showRechargeEntry ? Coins : Ticket}
label="泥点充值"
subLabel="充值泥点"
icon={Coins}
imageSrc={profileCoinsImage}
onClick={openRechargeOrRewardCodeModal}
/>
<ProfileShortcutButton
label="存档"
subLabel={
saveEntries.length > 0
? `${saveEntries.length}个可继续`
: '继续游玩'
}
icon={Archive}
onClick={() => setProfilePopupPanel('saveArchives')}
/>
{showRechargeEntry ? (
<ProfileShortcutButton
label="兑换码"
subLabel="福利奖励"
icon={Ticket}
onClick={openRewardCodeModal}
/>
) : null}
<ProfileShortcutButton
label="邀请好友"
subLabel={
<>
<span>30</span>
<Coins className="h-3 w-3" />
</>
}
subLabel="双方得 30 泥点"
icon={UserPlus}
imageSrc={profileInviteImage}
onClick={() => openProfilePopupPanel('invite')}
/>
{canShowReferralRedeemShortcut ? (
<ProfileShortcutButton
label="填邀请码"
subLabel="新用户奖励"
icon={Ticket}
onClick={() => openProfilePopupPanel('redeem')}
/>
) : null}
<ProfileShortcutButton
label="兑换码"
subLabel="领取福利"
icon={Ticket}
imageSrc={profileGiftImage}
onClick={openRewardCodeModal}
/>
<ProfileShortcutButton
label="玩家社区"
subLabel="每日领福利"
subLabel="交流心得 领取福利"
icon={MessageCircle}
imageSrc={profileCommunityImage}
onClick={() => openProfilePopupPanel('community')}
/>
<ProfileShortcutButton
label="反馈"
subLabel="问题与建议"
label="反馈与建议"
subLabel="帮助我们做得更好"
icon={MessageCircle}
imageSrc={profileFeedbackImage}
onClick={onOpenFeedback}
/>
</div>
</section>
<section className={`${PANEL_SURFACE_CLASS} px-4 py-3.5`}>
<button
type="button"
<section className="platform-profile-settings-panel" aria-label="设置入口">
<ProfileSettingsRow
label="主题设置"
icon={Palette}
onClick={() => authUi.openSettingsModal('appearance')}
/>
<ProfileSettingsRow
label="账号与安全"
icon={ShieldCheck}
onClick={() => authUi.openSettingsModal('account')}
/>
<ProfileSettingsRow
label="通用设置"
icon={Settings}
onClick={() => authUi.openSettingsModal()}
className="platform-subpanel platform-interactive-card flex w-full items-center justify-between gap-3 rounded-[1.25rem] px-4 py-4 text-left transition hover:border-[var(--platform-surface-hover-border)] hover:bg-[var(--platform-button-secondary-fill)]"
>
<div className="flex items-center gap-3">
<div className="platform-profile-chip flex h-10 w-10 items-center justify-center rounded-full">
<Settings className="h-[1.125rem] w-[1.125rem]" />
</div>
<div>
<div className="text-base font-semibold text-[var(--platform-text-strong)]">
</div>
<div className="text-xs text-[var(--platform-text-soft)]">
</div>
</div>
</div>
<ChevronRight className="h-4 w-4 text-[var(--platform-text-soft)]" />
</button>
/>
<ProfileSettingsRow
label="存档"
icon={Archive}
onClick={() => setProfilePopupPanel('saveArchives')}
/>
</section>
<section
className="platform-profile-secondary-shortcuts"
aria-label="次级入口"
>
<ProfileSecondaryShortcutButton
label="存档"
subLabel={
saveEntries.length > 0
? `${saveEntries.length}个可继续`
: '继续游玩'
}
icon={Archive}
onClick={() => setProfilePopupPanel('saveArchives')}
/>
{canShowReferralRedeemShortcut ? (
<ProfileSecondaryShortcutButton
label="填邀请码"
subLabel="新用户奖励"
icon={Ticket}
onClick={() => openProfilePopupPanel('redeem')}
/>
) : null}
</section>
<ProfileLegalSection onOpenDocument={setActiveLegalDocumentId} />
@@ -6566,7 +6706,19 @@ export function RpgEntryHomeView({
{!isMobileRecommendTab ? (
<div className="platform-mobile-topbar mb-3 flex shrink-0 items-center justify-between gap-3 px-0.5">
<RpgEntryBrandLogo />
{!isAuthenticated ? (
{isAuthenticated && activeTab === 'create' ? (
<button
type="button"
onClick={openUserSurface}
className="platform-mobile-create-wallet-chip inline-flex shrink-0 items-center gap-1.5 rounded-full border border-[#f0cfae] bg-[#fff5eb] px-2.5 py-1.5 text-xs font-black text-[#b65f2c] shadow-[0_10px_22px_rgba(174,111,73,0.12)]"
aria-label={`${formatDashboardCount(remainingNarrativeCoins)}泥点`}
>
<span className="grid h-6 w-6 place-items-center rounded-full bg-[#ffe0ab] text-[#cf7b34]">
<Coins className="h-3.5 w-3.5" />
</span>
<span>{formatDashboardCount(remainingNarrativeCoins)}</span>
</button>
) : !isAuthenticated ? (
<button
type="button"
onClick={openUserSurface}
@@ -6714,6 +6866,19 @@ export function RpgEntryHomeView({
</div>
<div className="flex items-center gap-3">
{isAuthenticated && activeTab === 'create' ? (
<button
type="button"
onClick={openUserSurface}
className="platform-desktop-create-wallet-chip platform-desktop-search inline-flex items-center gap-2 px-3 py-2.5 text-xs font-black text-[#b65f2c]"
aria-label={`${formatDashboardCount(remainingNarrativeCoins)}泥点`}
>
<span className="grid h-7 w-7 place-items-center rounded-full bg-[#ffe0ab] text-[#cf7b34]">
<Coins className="h-3.5 w-3.5" />
</span>
<span>{formatDashboardCount(remainingNarrativeCoins)}</span>
</button>
) : null}
<button
type="button"
onClick={openUserSurface}