继续收口账号与运行态弹窗壳层
账号设置弹窗复用认证壳层并保留 direct mode 唯一 dialog 语义 拼图运行态新增本地弹窗壳层收口确认设置退出失败和通关结算 抓大鹅与跳一跳结算弹窗提取本地结算结构并补测试 拼图 onboarding 登录保存覆盖层迁入 UnifiedModal 更新 PlatformUiKit 收口文档和 Hermes 决策记录
This commit is contained in:
93
src/components/puzzle-runtime/PuzzleRuntimeModalShell.tsx
Normal file
93
src/components/puzzle-runtime/PuzzleRuntimeModalShell.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import type {
|
||||
ButtonHTMLAttributes,
|
||||
MouseEvent,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
|
||||
type PuzzleRuntimeModalShellProps = {
|
||||
titleId: string;
|
||||
children: ReactNode;
|
||||
overlayClassName?: string;
|
||||
dialogClassName?: string;
|
||||
onOverlayClick?: () => void;
|
||||
};
|
||||
|
||||
type PuzzleRuntimeDialogFooterProps = {
|
||||
children: ReactNode;
|
||||
className: string;
|
||||
framed?: boolean;
|
||||
};
|
||||
|
||||
type PuzzleRuntimeDialogButtonProps =
|
||||
ButtonHTMLAttributes<HTMLButtonElement> & {
|
||||
tone: 'primary' | 'secondary';
|
||||
};
|
||||
|
||||
const DEFAULT_OVERLAY_CLASS_NAME =
|
||||
'puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-sm';
|
||||
|
||||
const DEFAULT_DIALOG_CLASS_NAME =
|
||||
'puzzle-runtime-dialog w-full max-w-[22rem] overflow-hidden rounded-[1.35rem] shadow-[0_24px_80px_rgba(0,0,0,0.24)]';
|
||||
|
||||
export function PuzzleRuntimeModalShell({
|
||||
titleId,
|
||||
children,
|
||||
overlayClassName = DEFAULT_OVERLAY_CLASS_NAME,
|
||||
dialogClassName = DEFAULT_DIALOG_CLASS_NAME,
|
||||
onOverlayClick,
|
||||
}: PuzzleRuntimeModalShellProps) {
|
||||
const handleDialogClick = (event: MouseEvent<HTMLElement>) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={overlayClassName}
|
||||
onClick={onOverlayClick ? () => onOverlayClick() : undefined}
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby={titleId}
|
||||
className={dialogClassName}
|
||||
onClick={handleDialogClick}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PuzzleRuntimeDialogFooter({
|
||||
children,
|
||||
className,
|
||||
framed = true,
|
||||
}: PuzzleRuntimeDialogFooterProps) {
|
||||
return (
|
||||
<footer
|
||||
className={`${framed ? 'puzzle-runtime-dialog__line border-t ' : ''}${className}`}
|
||||
>
|
||||
{children}
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export function PuzzleRuntimeDialogButton({
|
||||
tone,
|
||||
className = '',
|
||||
type = 'button',
|
||||
...buttonProps
|
||||
}: PuzzleRuntimeDialogButtonProps) {
|
||||
const toneClassName =
|
||||
tone === 'primary'
|
||||
? 'puzzle-runtime-primary-button'
|
||||
: 'puzzle-runtime-secondary-button';
|
||||
|
||||
return (
|
||||
<button
|
||||
{...buttonProps}
|
||||
type={type}
|
||||
className={`${toneClassName} ${className}`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -58,6 +58,11 @@ import { useAuthUi } from '../auth/AuthUiContext';
|
||||
import { PlatformRuntimeStatusToast } from '../common/PlatformRuntimeStatusToast';
|
||||
import { RuntimeResourcePendingMarker } from '../common/RuntimeResourcePendingMarker';
|
||||
import { ResolvedAssetImage } from '../ResolvedAssetImage';
|
||||
import {
|
||||
PuzzleRuntimeDialogButton,
|
||||
PuzzleRuntimeDialogFooter,
|
||||
PuzzleRuntimeModalShell,
|
||||
} from './PuzzleRuntimeModalShell';
|
||||
import {
|
||||
buildMergedGroupOutlinePath,
|
||||
buildRoundedGridCellClipPath,
|
||||
@@ -1372,13 +1377,11 @@ export function PuzzleRuntimeShell({
|
||||
};
|
||||
|
||||
const clearResultDialog = isClearResultOpen ? (
|
||||
<div className={clearResultOverlayClassName}>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="puzzle-clear-result-title"
|
||||
className="puzzle-runtime-dialog flex max-h-[min(92vh,42rem)] w-full max-w-[28rem] flex-col overflow-hidden rounded-[1.5rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<PuzzleRuntimeModalShell
|
||||
titleId="puzzle-clear-result-title"
|
||||
overlayClassName={clearResultOverlayClassName}
|
||||
dialogClassName="puzzle-runtime-dialog flex max-h-[min(92vh,42rem)] w-full max-w-[28rem] flex-col overflow-hidden rounded-[1.5rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<header className="puzzle-runtime-dialog__line flex items-start justify-between gap-3 border-b px-5 py-4">
|
||||
<div className="min-w-0">
|
||||
<div className="puzzle-runtime-primary-button mb-2 inline-flex h-9 w-9 items-center justify-center rounded-full">
|
||||
@@ -1394,16 +1397,17 @@ export function PuzzleRuntimeShell({
|
||||
{currentLevel.levelName}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
<PuzzleRuntimeDialogButton
|
||||
type="button"
|
||||
tone="secondary"
|
||||
aria-label="关闭通关弹窗"
|
||||
className="puzzle-runtime-secondary-button inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full transition hover:brightness-105"
|
||||
className="inline-flex h-9 w-9 shrink-0 items-center justify-center rounded-full transition hover:brightness-105"
|
||||
onClick={() => {
|
||||
setDismissedClearKey(clearResultKey);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</PuzzleRuntimeDialogButton>
|
||||
</header>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
|
||||
@@ -1493,7 +1497,7 @@ export function PuzzleRuntimeShell({
|
||||
</div>
|
||||
|
||||
{canAdvanceDefaultNextLevel ? (
|
||||
<footer className="puzzle-runtime-dialog__line flex items-center justify-end border-t px-5 py-4">
|
||||
<PuzzleRuntimeDialogFooter className="flex items-center justify-end px-5 py-4">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="下一关"
|
||||
@@ -1523,10 +1527,9 @@ export function PuzzleRuntimeShell({
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</footer>
|
||||
</PuzzleRuntimeDialogFooter>
|
||||
) : null}
|
||||
</section>
|
||||
</div>
|
||||
</PuzzleRuntimeModalShell>
|
||||
) : null;
|
||||
const clearResultLayer =
|
||||
embedded && clearResultDialog && typeof document !== 'undefined'
|
||||
@@ -2174,290 +2177,261 @@ export function PuzzleRuntimeShell({
|
||||
) : null}
|
||||
|
||||
{propDialog ? (
|
||||
<div
|
||||
className="puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center p-4 backdrop-blur-sm"
|
||||
onClick={() => {
|
||||
<PuzzleRuntimeModalShell
|
||||
titleId="puzzle-prop-confirm-title"
|
||||
onOverlayClick={() => {
|
||||
if (!isPropConfirming) {
|
||||
setPropDialog(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="puzzle-prop-confirm-title"
|
||||
className="puzzle-runtime-dialog w-full max-w-[22rem] overflow-hidden rounded-[1.35rem] shadow-[0_24px_80px_rgba(0,0,0,0.24)]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<header className="puzzle-runtime-dialog__line flex items-center gap-3 border-b px-5 py-4">
|
||||
<span className="puzzle-runtime-primary-button inline-flex h-9 w-9 items-center justify-center rounded-full">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
</span>
|
||||
<h2
|
||||
id="puzzle-prop-confirm-title"
|
||||
className="text-sm font-black"
|
||||
<header className="puzzle-runtime-dialog__line flex items-center gap-3 border-b px-5 py-4">
|
||||
<span className="puzzle-runtime-primary-button inline-flex h-9 w-9 items-center justify-center rounded-full">
|
||||
<Sparkles className="h-4 w-4" />
|
||||
</span>
|
||||
<h2
|
||||
id="puzzle-prop-confirm-title"
|
||||
className="text-sm font-black"
|
||||
>
|
||||
{propDialog.title}
|
||||
</h2>
|
||||
</header>
|
||||
<div className="puzzle-runtime-dialog__body px-5 py-4 text-sm">
|
||||
消耗 1 泥点
|
||||
{propConfirmError ? (
|
||||
<PlatformRuntimeStatusToast
|
||||
tone="error"
|
||||
size="xs"
|
||||
shape="rounded"
|
||||
className="mt-3"
|
||||
>
|
||||
{propDialog.title}
|
||||
</h2>
|
||||
</header>
|
||||
<div className="puzzle-runtime-dialog__body px-5 py-4 text-sm">
|
||||
消耗 1 泥点
|
||||
{propConfirmError ? (
|
||||
<PlatformRuntimeStatusToast
|
||||
tone="error"
|
||||
size="xs"
|
||||
shape="rounded"
|
||||
className="mt-3"
|
||||
>
|
||||
{propConfirmError}
|
||||
</PlatformRuntimeStatusToast>
|
||||
{propConfirmError}
|
||||
</PlatformRuntimeStatusToast>
|
||||
) : null}
|
||||
</div>
|
||||
<PuzzleRuntimeDialogFooter className="flex items-center justify-end gap-3 px-5 py-4">
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="secondary"
|
||||
onClick={() => setPropDialog(null)}
|
||||
disabled={isPropConfirming}
|
||||
className="rounded-full px-4 py-2 text-xs font-bold transition hover:brightness-105"
|
||||
>
|
||||
取消
|
||||
</PuzzleRuntimeDialogButton>
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="primary"
|
||||
disabled={isPropConfirming}
|
||||
onClick={() => {
|
||||
void handleConfirmProp();
|
||||
}}
|
||||
className="inline-flex items-center gap-2 rounded-full px-5 py-2 text-sm font-black transition hover:brightness-105 disabled:opacity-60"
|
||||
>
|
||||
{isPropConfirming ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : null}
|
||||
</div>
|
||||
<footer className="puzzle-runtime-dialog__line flex items-center justify-end gap-3 border-t px-5 py-4">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPropDialog(null)}
|
||||
disabled={isPropConfirming}
|
||||
className="puzzle-runtime-secondary-button rounded-full px-4 py-2 text-xs font-bold transition hover:brightness-105"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isPropConfirming}
|
||||
onClick={() => {
|
||||
void handleConfirmProp();
|
||||
}}
|
||||
className="puzzle-runtime-primary-button inline-flex items-center gap-2 rounded-full px-5 py-2 text-sm font-black transition hover:brightness-105 disabled:opacity-60"
|
||||
>
|
||||
{isPropConfirming ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : null}
|
||||
确定
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
确定
|
||||
</PuzzleRuntimeDialogButton>
|
||||
</PuzzleRuntimeDialogFooter>
|
||||
</PuzzleRuntimeModalShell>
|
||||
) : null}
|
||||
|
||||
{isSettingsPanelOpen ? (
|
||||
<div
|
||||
className="puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center p-3 backdrop-blur-sm sm:p-4"
|
||||
onClick={() => setIsSettingsPanelOpen(false)}
|
||||
<PuzzleRuntimeModalShell
|
||||
titleId="puzzle-settings-title"
|
||||
overlayClassName="puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center p-3 backdrop-blur-sm sm:p-4"
|
||||
dialogClassName="puzzle-runtime-dialog flex max-h-[min(88vh,36rem)] w-full max-w-md flex-col overflow-hidden rounded-[1.5rem] shadow-[0_24px_80px_rgba(0,0,0,0.24)]"
|
||||
onOverlayClick={() => setIsSettingsPanelOpen(false)}
|
||||
>
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="puzzle-settings-title"
|
||||
className="puzzle-runtime-dialog flex max-h-[min(88vh,36rem)] w-full max-w-md flex-col overflow-hidden rounded-[1.5rem] shadow-[0_24px_80px_rgba(0,0,0,0.24)]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<header className="puzzle-runtime-dialog__line relative border-b px-4 py-3 sm:px-5 sm:py-4">
|
||||
<div className="min-w-0 pr-10">
|
||||
<h2
|
||||
id="puzzle-settings-title"
|
||||
className="text-sm font-semibold"
|
||||
>
|
||||
拼图设置
|
||||
</h2>
|
||||
<div className="puzzle-runtime-dialog__soft mt-1 text-[11px]">
|
||||
{hideExitControls
|
||||
? '调整音乐音量,查看本局进度。'
|
||||
: '调整音乐音量,查看本局进度,或返回上一页。'}
|
||||
</div>
|
||||
<header className="puzzle-runtime-dialog__line relative border-b px-4 py-3 sm:px-5 sm:py-4">
|
||||
<div className="min-w-0 pr-10">
|
||||
<h2 id="puzzle-settings-title" className="text-sm font-semibold">
|
||||
拼图设置
|
||||
</h2>
|
||||
<div className="puzzle-runtime-dialog__soft mt-1 text-[11px]">
|
||||
{hideExitControls
|
||||
? '调整音乐音量,查看本局进度。'
|
||||
: '调整音乐音量,查看本局进度,或返回上一页。'}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭拼图设置"
|
||||
onClick={() => setIsSettingsPanelOpen(false)}
|
||||
className="puzzle-runtime-dialog__soft absolute right-4 top-3 p-1 transition-colors hover:brightness-75 sm:right-5 sm:top-4"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭拼图设置"
|
||||
onClick={() => setIsSettingsPanelOpen(false)}
|
||||
className="puzzle-runtime-dialog__soft absolute right-4 top-3 p-1 transition-colors hover:brightness-75 sm:right-5 sm:top-4"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
|
||||
<div className="puzzle-runtime-settings-card rounded-2xl p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="puzzle-runtime-tool-button__cool text-[10px] tracking-[0.24em]">
|
||||
音频
|
||||
</div>
|
||||
<div className="mt-2 text-sm font-semibold">音乐音量</div>
|
||||
</div>
|
||||
<div className="puzzle-runtime-pill rounded-full px-2 py-1 text-xs">
|
||||
{Math.round(musicVolume * 100)}%
|
||||
<div className="min-h-0 flex-1 space-y-4 overflow-y-auto p-4">
|
||||
<div className="puzzle-runtime-settings-card rounded-2xl p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<div className="puzzle-runtime-tool-button__cool text-[10px] tracking-[0.24em]">
|
||||
音频
|
||||
</div>
|
||||
<div className="mt-2 text-sm font-semibold">音乐音量</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
aria-label="拼图音乐音量"
|
||||
value={Math.round(musicVolume * 100)}
|
||||
onChange={(event) =>
|
||||
onMusicVolumeChange(
|
||||
Number(event.currentTarget.value) / 100,
|
||||
)
|
||||
}
|
||||
className="h-2 w-full cursor-pointer accent-[var(--puzzle-runtime-accent-text)]"
|
||||
/>
|
||||
<div className="puzzle-runtime-pill rounded-full px-2 py-1 text-xs">
|
||||
{Math.round(musicVolume * 100)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="puzzle-runtime-settings-card rounded-2xl px-4 py-3">
|
||||
<div className="puzzle-runtime-dialog__soft text-[10px] uppercase tracking-[0.18em]">
|
||||
本局进度
|
||||
</div>
|
||||
<div className="puzzle-runtime-dialog__body mt-3 space-y-2 text-sm">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">关卡</span>
|
||||
<span className="font-semibold">{levelLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">
|
||||
已完成关卡
|
||||
</span>
|
||||
<span className="font-semibold">
|
||||
{run.clearedLevelCount}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">
|
||||
当前状态
|
||||
</span>
|
||||
<span className="font-semibold">{statusLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">
|
||||
当前用时
|
||||
</span>
|
||||
<span className="font-mono font-semibold">
|
||||
{formatElapsedMs(displayElapsedMs)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex items-center gap-3">
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
aria-label="拼图音乐音量"
|
||||
value={Math.round(musicVolume * 100)}
|
||||
onChange={(event) =>
|
||||
onMusicVolumeChange(Number(event.currentTarget.value) / 100)
|
||||
}
|
||||
className="h-2 w-full cursor-pointer accent-[var(--puzzle-runtime-accent-text)]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer className="puzzle-runtime-dialog__line flex items-center justify-end gap-3 border-t px-4 py-3 sm:px-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsSettingsPanelOpen(false)}
|
||||
className="puzzle-runtime-secondary-button rounded-full px-3 py-1.5 text-[11px] transition hover:brightness-105"
|
||||
<div className="puzzle-runtime-settings-card rounded-2xl px-4 py-3">
|
||||
<div className="puzzle-runtime-dialog__soft text-[10px] uppercase tracking-[0.18em]">
|
||||
本局进度
|
||||
</div>
|
||||
<div className="puzzle-runtime-dialog__body mt-3 space-y-2 text-sm">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">关卡</span>
|
||||
<span className="font-semibold">{levelLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">
|
||||
已完成关卡
|
||||
</span>
|
||||
<span className="font-semibold">{run.clearedLevelCount}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">当前状态</span>
|
||||
<span className="font-semibold">{statusLabel}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<span className="puzzle-runtime-dialog__soft">当前用时</span>
|
||||
<span className="font-mono font-semibold">
|
||||
{formatElapsedMs(displayElapsedMs)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PuzzleRuntimeDialogFooter className="flex items-center justify-end gap-3 px-4 py-3 sm:px-5">
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="secondary"
|
||||
onClick={() => setIsSettingsPanelOpen(false)}
|
||||
className="rounded-full px-3 py-1.5 text-[11px] transition hover:brightness-105"
|
||||
>
|
||||
继续拼图
|
||||
</PuzzleRuntimeDialogButton>
|
||||
{!hideExitControls ? (
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="primary"
|
||||
onClick={() => {
|
||||
setIsSettingsPanelOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
className={`rounded-full px-4 py-2 text-sm font-bold transition hover:brightness-105 ${
|
||||
shouldHideBackButton ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
继续拼图
|
||||
</button>
|
||||
{!hideExitControls ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsSettingsPanelOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
className={`puzzle-runtime-primary-button rounded-full px-4 py-2 text-sm font-bold transition hover:brightness-105 ${
|
||||
shouldHideBackButton ? 'hidden' : ''
|
||||
}`}
|
||||
>
|
||||
返回上一页
|
||||
</button>
|
||||
) : null}
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
返回上一页
|
||||
</PuzzleRuntimeDialogButton>
|
||||
) : null}
|
||||
</PuzzleRuntimeDialogFooter>
|
||||
</PuzzleRuntimeModalShell>
|
||||
) : null}
|
||||
|
||||
{isExitRemodelPromptOpen && !hideExitControls ? (
|
||||
<div className="puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center px-4 py-6 backdrop-blur-md">
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="puzzle-exit-remodel-title"
|
||||
className="puzzle-runtime-dialog relative flex w-full max-w-[21rem] flex-col overflow-hidden rounded-[1.35rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
<PuzzleRuntimeModalShell
|
||||
titleId="puzzle-exit-remodel-title"
|
||||
overlayClassName="puzzle-runtime-modal-overlay absolute inset-0 z-50 flex items-center justify-center px-4 py-6 backdrop-blur-md"
|
||||
dialogClassName="puzzle-runtime-dialog relative flex w-full max-w-[21rem] flex-col overflow-hidden rounded-[1.35rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-[var(--puzzle-runtime-accent-text)] to-transparent" />
|
||||
<header className="flex flex-col items-center px-6 pt-7 text-center">
|
||||
<div className="puzzle-runtime-stat-card mb-4 grid h-14 w-14 place-items-center rounded-2xl">
|
||||
<Sparkles className="puzzle-runtime-tool-button__warm h-7 w-7" />
|
||||
</div>
|
||||
<h2
|
||||
id="puzzle-exit-remodel-title"
|
||||
className="text-[1.75rem] font-black leading-[1.08]"
|
||||
>
|
||||
体验不佳?
|
||||
<br />
|
||||
试试改造功能!
|
||||
</h2>
|
||||
</header>
|
||||
<PuzzleRuntimeDialogFooter
|
||||
className="grid gap-3 px-5 pb-5 pt-6"
|
||||
framed={false}
|
||||
>
|
||||
<div className="pointer-events-none absolute inset-x-8 top-0 h-px bg-gradient-to-r from-transparent via-[var(--puzzle-runtime-accent-text)] to-transparent" />
|
||||
<header className="flex flex-col items-center px-6 pt-7 text-center">
|
||||
<div className="puzzle-runtime-stat-card mb-4 grid h-14 w-14 place-items-center rounded-2xl">
|
||||
<Sparkles className="puzzle-runtime-tool-button__warm h-7 w-7" />
|
||||
</div>
|
||||
<h2
|
||||
id="puzzle-exit-remodel-title"
|
||||
className="text-[1.75rem] font-black leading-[1.08]"
|
||||
>
|
||||
体验不佳?
|
||||
<br />
|
||||
试试改造功能!
|
||||
</h2>
|
||||
</header>
|
||||
<footer className="grid gap-3 px-5 pb-5 pt-6">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => {
|
||||
setIsExitRemodelPromptOpen(false);
|
||||
void onRemodelWork?.(exitPromptProfileId);
|
||||
}}
|
||||
className="puzzle-runtime-primary-button min-h-[3.25rem] rounded-2xl px-5 text-sm font-black transition hover:brightness-105 active:translate-y-px disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
作品改造
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsExitRemodelPromptOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
className="puzzle-runtime-secondary-button min-h-[3rem] rounded-2xl px-5 text-sm font-bold transition hover:brightness-105 active:translate-y-px"
|
||||
>
|
||||
保存并退出
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="primary"
|
||||
disabled={isBusy}
|
||||
onClick={() => {
|
||||
setIsExitRemodelPromptOpen(false);
|
||||
void onRemodelWork?.(exitPromptProfileId);
|
||||
}}
|
||||
className="min-h-[3.25rem] rounded-2xl px-5 text-sm font-black transition hover:brightness-105 active:translate-y-px disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
作品改造
|
||||
</PuzzleRuntimeDialogButton>
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="secondary"
|
||||
onClick={() => {
|
||||
setIsExitRemodelPromptOpen(false);
|
||||
onBack();
|
||||
}}
|
||||
className="min-h-[3rem] rounded-2xl px-5 text-sm font-bold transition hover:brightness-105 active:translate-y-px"
|
||||
>
|
||||
保存并退出
|
||||
</PuzzleRuntimeDialogButton>
|
||||
</PuzzleRuntimeDialogFooter>
|
||||
</PuzzleRuntimeModalShell>
|
||||
) : null}
|
||||
|
||||
{runtimeStatus === 'failed' ? (
|
||||
<div className="puzzle-runtime-modal-overlay absolute inset-0 z-40 flex items-center justify-center px-4 py-6 backdrop-blur-sm">
|
||||
<section
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="puzzle-failed-title"
|
||||
className="puzzle-runtime-dialog flex w-full max-w-[24rem] flex-col overflow-hidden rounded-[1.5rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<header className="puzzle-runtime-dialog__line border-b px-5 py-4">
|
||||
<h2 id="puzzle-failed-title" className="text-lg font-black">
|
||||
关卡失败
|
||||
</h2>
|
||||
<div className="puzzle-runtime-dialog__soft mt-1 text-xs">
|
||||
{currentLevel.levelName}
|
||||
</div>
|
||||
</header>
|
||||
<footer className="puzzle-runtime-dialog__line grid grid-cols-2 gap-3 border-t px-5 py-4">
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => {
|
||||
void onRestartLevel?.();
|
||||
}}
|
||||
className="puzzle-runtime-secondary-button rounded-full px-4 py-2.5 text-sm font-black transition hover:brightness-105 disabled:opacity-50"
|
||||
>
|
||||
重新开始
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isBusy}
|
||||
onClick={() => openPropDialog('extendTime', '继续1分钟')}
|
||||
className="puzzle-runtime-primary-button rounded-full px-4 py-2.5 text-sm font-black transition hover:brightness-105 disabled:opacity-50"
|
||||
>
|
||||
继续1分钟
|
||||
</button>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
<PuzzleRuntimeModalShell
|
||||
titleId="puzzle-failed-title"
|
||||
overlayClassName="puzzle-runtime-modal-overlay absolute inset-0 z-40 flex items-center justify-center px-4 py-6 backdrop-blur-sm"
|
||||
dialogClassName="puzzle-runtime-dialog flex w-full max-w-[24rem] flex-col overflow-hidden rounded-[1.5rem] shadow-[0_28px_90px_rgba(0,0,0,0.28)]"
|
||||
>
|
||||
<header className="puzzle-runtime-dialog__line border-b px-5 py-4">
|
||||
<h2 id="puzzle-failed-title" className="text-lg font-black">
|
||||
关卡失败
|
||||
</h2>
|
||||
<div className="puzzle-runtime-dialog__soft mt-1 text-xs">
|
||||
{currentLevel.levelName}
|
||||
</div>
|
||||
</header>
|
||||
<PuzzleRuntimeDialogFooter className="grid grid-cols-2 gap-3 px-5 py-4">
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="secondary"
|
||||
disabled={isBusy}
|
||||
onClick={() => {
|
||||
void onRestartLevel?.();
|
||||
}}
|
||||
className="rounded-full px-4 py-2.5 text-sm font-black transition hover:brightness-105 disabled:opacity-50"
|
||||
>
|
||||
重新开始
|
||||
</PuzzleRuntimeDialogButton>
|
||||
<PuzzleRuntimeDialogButton
|
||||
tone="primary"
|
||||
disabled={isBusy}
|
||||
onClick={() => openPropDialog('extendTime', '继续1分钟')}
|
||||
className="rounded-full px-4 py-2.5 text-sm font-black transition hover:brightness-105 disabled:opacity-50"
|
||||
>
|
||||
继续1分钟
|
||||
</PuzzleRuntimeDialogButton>
|
||||
</PuzzleRuntimeDialogFooter>
|
||||
</PuzzleRuntimeModalShell>
|
||||
) : null}
|
||||
|
||||
{clearResultLayer}
|
||||
|
||||
Reference in New Issue
Block a user