feat: add puzzle residence configuration and reflection progress logic

- Introduced `puzzleResidences.ts` to define puzzle residence configurations including properties like display name, position, and puzzle availability.
- Added `reflectionProgress.ts` to manage badge unlocking logic based on place completion, including functions to determine unlocked badges and video sources for scenes.
This commit is contained in:
zhongyidian committed 2026-04-27 11:49:30 +08:00
1 parent e0fa47d21a
commit f5f36f75b5
153 files changed
+3846 -2352

No files matched your search

+31 -10
View File
@@ -1,5 +1,5 @@
import { cn, getStaticImage } from '@/lib/utils'
import { useEffect, useMemo, useState } from 'react'
import { type ReactNode, useEffect, useMemo, useState } from 'react'
import { FlipActionButton, Image } from '@/components'
import { motion } from 'framer-motion'
@@ -10,6 +10,14 @@ const OVERLAY_STYLE: React.CSSProperties = {
pointerEvents: 'none',
}
const isDirectImageSrc = (src: string) =>
src.startsWith('http://') ||
src.startsWith('https://') ||
src.startsWith('/') ||
src.startsWith('data:image/')
const resolveImageSrc = (src: string) => (isDirectImageSrc(src) ? src : getStaticImage(src))
export interface ExhibitFlipOverlayProps {
/** 展品正面图片地址,可以是静态资源路径或完整 URL */
src: string
@@ -29,6 +37,10 @@ export interface ExhibitFlipOverlayProps {
visible?: boolean
/** 额外的自定义类 */
className?: string
/** 底部操作区 */
footer?: ReactNode
/** 底部操作区额外类名 */
footerClassName?: string
}
/**
@@ -45,6 +57,8 @@ const ExhibitFlipOverlay = ({
onClose,
visible = true,
className,
footer,
footerClassName,
}: ExhibitFlipOverlayProps) => {
const backSrc = getStaticImage('components/close')
@@ -63,18 +77,11 @@ const ExhibitFlipOverlay = ({
}
}, [visible, src, flipSrc])
const frontImageSrc =
src.startsWith('http://') || src.startsWith('https://') || src.startsWith('/')
? src
: getStaticImage(src)
const frontImageSrc = resolveImageSrc(src)
const backImageSrc = useMemo(() => {
if (!flipSrc) return null
return flipSrc.startsWith('http://') ||
flipSrc.startsWith('https://') ||
flipSrc.startsWith('/')
? flipSrc
: getStaticImage(flipSrc)
return resolveImageSrc(flipSrc)
}, [flipSrc])
const canFlip = Boolean(backImageSrc && backImageSrc !== frontImageSrc)
@@ -200,6 +207,20 @@ const ExhibitFlipOverlay = ({
</motion.div>
)}
</div>
{showChrome && footer && (
<motion.div
className={cn(
'absolute left-1/2 bottom-38 z-30 -translate-x-1/2 pointer-events-auto',
footerClassName
)}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.22, ease: 'easeOut' }}
>
{footer}
</motion.div>
)}
</div>
</div>
)