From 3178d880f20d9affd1d644527fbc115230b9152f Mon Sep 17 00:00:00 2001 From: gahusb Date: Wed, 1 Apr 2026 22:44:17 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8A=9C=ED=86=A0=EB=A6=AC=EC=96=BC=20?= =?UTF-8?q?data-tutorial=20=EB=A7=88=EC=BB=A4=20=EB=B0=8F=20TutorialOverla?= =?UTF-8?q?y=20=EC=95=B1=20=ED=86=B5=ED=95=A9=20(JSA-47)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ElementsScreen: 첫 번째 obtained 원소 카드에 data-tutorial 스포트라이트 마커 - FusionScreen: 합성 슬롯1, 합성 결과 배너에 data-tutorial 마커 - EvolutionScreen: 첫 번째 강화 버튼에 data-tutorial 마커 - _app.tsx: AppContainer에 전역 마운트 Co-Authored-By: Paperclip --- src/_app.tsx | 2 ++ src/components/screens/ElementsScreen.tsx | 13 +++++++++++-- src/components/screens/EvolutionScreen.tsx | 3 ++- src/components/screens/FusionScreen.tsx | 6 +++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/_app.tsx b/src/_app.tsx index 1437b51..c3a3d55 100644 --- a/src/_app.tsx +++ b/src/_app.tsx @@ -4,6 +4,7 @@ import { PropsWithChildren } from 'react'; import { InitialProps } from '@granite-js/react-native'; import { context } from '../require.context'; import { initAnalytics } from './analytics'; +import { TutorialOverlay } from './components/tutorial/TutorialOverlay'; initAnalytics(process.env.NODE_ENV !== 'production'); @@ -11,6 +12,7 @@ function AppContainer({ children }: PropsWithChildren) { return ( {children} + ); } diff --git a/src/components/screens/ElementsScreen.tsx b/src/components/screens/ElementsScreen.tsx index 25a0219..d1b067b 100644 --- a/src/components/screens/ElementsScreen.tsx +++ b/src/components/screens/ElementsScreen.tsx @@ -469,9 +469,10 @@ interface ElementCardProps { count: number; level: number; onSelect: (el: ElementData) => void; + isTutorialTarget?: boolean; } -const ElementCard = memo(function ElementCard({ el, state, count, level, onSelect }: ElementCardProps) { +const ElementCard = memo(function ElementCard({ el, state, count, level, onSelect, isTutorialTarget }: ElementCardProps) { const spawnRate = calcSpawnRate(el.id, level); if (state === 'undiscovered') { @@ -498,7 +499,11 @@ const ElementCard = memo(function ElementCard({ el, state, count, level, onSelec } return ( -
onSelect(el)}> +
onSelect(el)} + data-tutorial={isTutorialTarget ? 'elements-first-card' : undefined} + >
@@ -593,6 +598,9 @@ export function ElementsScreen() { items: elementsData.filter((el) => el.tier === tier), })); + // 첫 번째 obtained 원소 ID (튜토리얼 스포트라이트용) + const firstObtainedId = obtainedElements[0]?.id ?? null; + const unlockRatio = obtainedElements.length / totalElements; return ( @@ -644,6 +652,7 @@ export function ElementsScreen() { count={elements[el.id] ?? 0} level={elementLevels[el.id] ?? 0} onSelect={setSelectedEl} + isTutorialTarget={el.id === firstObtainedId} /> ))}
diff --git a/src/components/screens/EvolutionScreen.tsx b/src/components/screens/EvolutionScreen.tsx index 7e5ccc3..f3e37de 100644 --- a/src/components/screens/EvolutionScreen.tsx +++ b/src/components/screens/EvolutionScreen.tsx @@ -220,7 +220,7 @@ export function EvolutionScreen() {
💰 {gold}
- {ownedElements.map((el) => { + {ownedElements.map((el, index) => { const level = elementLevels[el.id] ?? 0; const isMax = level >= ENHANCE_MAX_LEVEL; const cost = isMax ? 0 : calcEnhanceCost(level); @@ -257,6 +257,7 @@ export function EvolutionScreen() { css={enhanceButtonStyle(canEnhance)} onClick={(e) => handleEnhance(el.id, e.currentTarget)} disabled={!canEnhance} + data-tutorial={index === 0 ? 'elements-enhance-button' : undefined} > {isMax ? '✅ 최대 레벨 달성' diff --git a/src/components/screens/FusionScreen.tsx b/src/components/screens/FusionScreen.tsx index 0d311f5..fb3edf9 100644 --- a/src/components/screens/FusionScreen.tsx +++ b/src/components/screens/FusionScreen.tsx @@ -502,6 +502,7 @@ export function FusionScreen() {
handleSlotClick(1)} + data-tutorial="fusion-slot1" > {slot1 ? ( <> @@ -546,7 +547,10 @@ export function FusionScreen() { {/* 합성 결과 피드백 */} {lastResult?.type === 'success' && lastResult.resultId && ( -
+
{elementMap[lastResult.resultId]?.emoji}