feat: update residence learning flows

Commit non-media application and asset changes while leaving audio, video, and environment files out of version control.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.7 committed 2026-05-13 10:15:26 +08:00
1 parent 68ba22a33f
commit 6d40e19346
184 files changed
+7663 -2203

No files matched your search

+64 -16
View File
@@ -1,9 +1,9 @@
import { EndingOverlay, Image, StudyGuideModal } from '@/components'
import { EndingOverlay, Image, StudyGuideModal, Video } from '@/components'
import { cn, getBackgroundImage, getPublicPath, getStaticImage } from '@/lib/utils'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { LOBBY_COLUMNS, LOBBY_LAYOUT, LOBBY_TIMELINE } from './lobby.config'
import { useNieerStore } from '@/store/nieerStore'
import { saveNieerStore, useNieerStore } from '@/store/nieerStore'
import { useDialogReducer } from './useDialogReducer'
import DialogLayer from './DialogLayer'
import {
@@ -15,11 +15,16 @@ import {
getUnlockVideoSrcForScene,
getUnlockedByIdMap,
} from './reflectionProgress'
import { usePuzzleAnimation } from '@/contexts/PuzzleAnimationContext'
import { setCourseTimeProgress } from '@/lib/utils/api-common'
const starName = 'nieer'
const NieerReflection = () => {
const navigate = useNavigate()
const location = useLocation()
const fromReflectionSubpage = !!(location.state as { fromReflectionSubpage?: boolean } | null)
?.fromReflectionSubpage
const { moduleName } = useParams<{ moduleName: string }>()
const debugLobby = new URLSearchParams(window.location.search).get('debugLobby') === '1'
const visitedReflectionCursors = useNieerStore((state) => state.visitedReflectionCursors)
@@ -27,7 +32,14 @@ const NieerReflection = () => {
const setReflectionWork = useNieerStore((state) => state.setReflectionWork)
const hasSeenReflectionGuide = useNieerStore((state) => state.hasSeenReflectionGuide)
const markReflectionGuideSeen = useNieerStore((state) => state.markReflectionGuideSeen)
const showGuideModal = !hasSeenReflectionGuide
const hasSeenBoxHint = useNieerStore((state) => state.hasSeenBoxHint)
const markBoxHintSeen = useNieerStore((state) => state.markBoxHintSeen)
const [showOpeningVideo, setShowOpeningVideo] = useState(!fromReflectionSubpage)
const showGuideModal = !showOpeningVideo && !hasSeenReflectionGuide
const showBoxHint = !showOpeningVideo && !showGuideModal && !hasSeenBoxHint
// 获取碎片动画触发方法
const { triggerPuzzleAnimation } = usePuzzleAnimation()
const {
state: dialogState,
@@ -59,10 +71,11 @@ const NieerReflection = () => {
dialogState.mode === 'idle' &&
allPlacesCompleted
) {
triggerPuzzleAnimation(14)
setEndingVisible(true)
}
prevDialogModeRef.current = dialogState.mode
}, [dialogState.mode, allPlacesCompleted])
}, [dialogState.mode, allPlacesCompleted, triggerPuzzleAnimation])
const handleResultShare = (sceneId: number, payload?: Record<string, unknown>) => {
handleShare({
@@ -78,6 +91,21 @@ const NieerReflection = () => {
className="w-full h-full relative bg-cover bg-center bg-no-repeat overflow-hidden"
style={{ backgroundImage: getBackgroundImage(`${starName}/reflection/lobby/场景首页底图`) }}
>
{showOpeningVideo && (
<div className="fixed inset-0 z-10000 w-full h-full bg-black">
<Video
src={getPublicPath('video/reflection/nieer_tiwu_00.mp4')}
autoPlay
playsInline
showSkipButton
skipText="跳过"
onEnded={() => setShowOpeningVideo(false)}
onSkip={() => setShowOpeningVideo(false)}
onError={() => setShowOpeningVideo(false)}
/>
</div>
)}
<Image
className="absolute right-6 top-5 z-30"
src={getStaticImage('components/返回')}
@@ -198,7 +226,7 @@ const NieerReflection = () => {
</div>
<div
className="absolute left-0 bottom-0 w-full h-[201px] z-20 flex justify-center items-center pt-20 gap-10"
className="absolute left-0 bottom-0 w-full h-[201px] z-1 flex justify-center items-center pt-20 gap-10 pointer-events-none"
style={{
backgroundImage: getBackgroundImage(`${starName}/reflection/lobby/footer`),
}}
@@ -227,13 +255,30 @@ const NieerReflection = () => {
</div>
</div>
<Image
button
src={getStaticImage(`${starName}/reflection/lobby/icon/旅行箱`)}
className="absolute right-0 bottom-0 z-30"
alt="旅行箱"
onClick={() => navigate(`/${moduleName}/${starName}/reflection/box`)}
/>
<div className="absolute right-0 bottom-0 z-30">
{showBoxHint && (
<>
<div className="absolute -left-85 -top-45 flex flex-col items-center bg-[#3a2a1a]/85 rounded-xl px-8 py-5 border-2 border-[#fcc419] animate-pulse">
<span className="text-4xl text-white font-bold [text-shadow:_0_0_10px_rgba(252,196,25,0.8)] whitespace-nowrap">
点击「领取旅行箱」
</span>
<br />
<span className="text-3xl text-[#fcc419] font-bold [text-shadow:_0_0_10px_rgba(0,0,0,0.8)] whitespace-nowrap">
旅行箱会记录你每段旅行的成果哦!
</span>
</div>
</>
)}
<Image
button
src={getStaticImage(`${starName}/reflection/lobby/icon/旅行箱`)}
alt="旅行箱"
onClick={() => {
markBoxHintSeen()
navigate(`/${moduleName}/${starName}/reflection/box`)
}}
/>
</div>
{debugLobby && (
<div
@@ -260,9 +305,10 @@ const NieerReflection = () => {
audioSrc={getPublicPath('audio/nieer/mp3_nieer_28.mp3')}
firstLineIndent="2em"
>
{`同学你好,欢迎来到“跨越时空·体悟”学习模块!
请登陆“时空回溯・亲历时代”体验仓,下一站:1936年,上海。
请带上你的旅行包,穿越时空解锁6个国歌故事,集齐精神徽章,绘制属于你的国歌精神图谱吧~时空门已开,请出发吧!`}
{`同学你好,欢迎来到”跨越时空·体悟”学习模块!
请登陆”时空回溯・亲历时代”体验仓,下一站:1936年,上海。
请带上你的旅行包,穿越时空解锁6个国歌故事,集齐精神徽章,绘制属于你的国歌精神图谱吧~
别忘了右下角的旅行箱哦,它会记录你每段旅行的成果!时空门已开,请出发吧!`}
</StudyGuideModal>
<EndingOverlay
@@ -272,6 +318,8 @@ const NieerReflection = () => {
text={`恭喜你完成所有时空旅行!成功绘制“国歌精神图谱”,解锁6个跨越时空的国歌故事~国歌的故事还在继续,它的精神藏在每一次唱响、每一次坚守中。愿你带着这份精神力量,做国歌的传承者、爱国的践行者!`}
onContinue={() => {
setEndingVisible(false)
saveNieerStore()
setCourseTimeProgress(14)
navigate(`/${moduleName}/${starName}/home`)
}}
/>