diff --git a/apps/farminglog/src/hooks/useTallPage.ts b/apps/farminglog/src/hooks/useTallPage.ts new file mode 100644 index 00000000..50c31eb6 --- /dev/null +++ b/apps/farminglog/src/hooks/useTallPage.ts @@ -0,0 +1,77 @@ +import { useEffect, useState } from "react"; + +/** + * 문서 전체 높이(가 임계값을 넘는지 감지하는 훅입니다 + * 기본 임계값 3000px이며, 리사이즈/레이아웃 변경에 반응 + */ +export default function useTallPage(threshold: number = 3000): boolean { + const [isTall, setIsTall] = useState(false); + + useEffect(() => { + const getPageHeight = () => document.documentElement.scrollHeight; + + // 동일 프레임 내 여러번 호출 방지 + let rafId: number | null = null; + const evaluate = () => { + if (rafId != null) return; + rafId = window.requestAnimationFrame(() => { + rafId = null; + setIsTall(getPageHeight() > threshold); + }); + }; + + // 최초 1회 평가 + evaluate(); + + let ro: ResizeObserver | null = null; + let bodyRo: ResizeObserver | null = null; + let mo: MutationObserver | null = null; + + // 레이아웃 변화에 반응함 + if (typeof ResizeObserver !== "undefined") { + ro = new ResizeObserver(() => { + evaluate(); + }); + ro.observe(document.documentElement); + + // body 높이 변화도 감지함 + if (document.body) { + bodyRo = new ResizeObserver(() => { + evaluate(); + }); + bodyRo.observe(document.body); + } + } else { + // fallback 윈도우 리사이즈 시 반응함 + window.addEventListener("resize", evaluate); + } + + // DOM 변동에 반응함 + if (typeof MutationObserver !== "undefined" && document.body) { + mo = new MutationObserver(() => { + evaluate(); + }); + mo.observe(document.body, { + subtree: true, + childList: true, + attributes: false, + }); + } + + // 추가 이벤트: 방향 전환 등 + window.addEventListener("orientationchange", evaluate); + + return () => { + if (ro) ro.disconnect(); + if (bodyRo) bodyRo.disconnect(); + if (mo) mo.disconnect(); + else window.removeEventListener("resize", evaluate); + window.removeEventListener("orientationchange", evaluate); + if (rafId != null) cancelAnimationFrame(rafId); + }; + }, [threshold]); + + return isTall; +} + + diff --git a/apps/farminglog/src/pages/game/index.styled.ts b/apps/farminglog/src/pages/game/index.styled.ts index c2800578..2772d704 100644 --- a/apps/farminglog/src/pages/game/index.styled.ts +++ b/apps/farminglog/src/pages/game/index.styled.ts @@ -121,4 +121,43 @@ export const StartButton = styled.button` transform: scale(1.05); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); } +`; + +// 랜딩 페이지용 배경 히어로 +export const LandingHero = styled.div<{ + $bgDesktop: string; + $bgMobile?: string; +}>` + width: 1200px; + margin: 0 auto; + height: 35000px; + + + background: ${({ $bgDesktop }) => `url(${$bgDesktop}) center / contain no-repeat`}; + + + // 모바일에선 잘모르겠음 일단 때리쳐! + @media (max-width: 768px) { + aspect-ratio: 750 / 2237; + background-image: ${({ $bgDesktop, $bgMobile }) => + `url(${($bgMobile && $bgMobile.length > 0) ? $bgMobile : $bgDesktop})`}; + background-size: contain; + background-position: center; + } +`; + +export const UpButton = styled.div` + position: fixed; + bottom: 70px; + right: 70px; + width: 150px; + height: 150px; + border-radius: 50%; + cursor: pointer; +`; + +export const UpButtonImage = styled.img` + width: 150px; + height: 150px; + object-fit: fill; `; \ No newline at end of file diff --git a/apps/farminglog/src/pages/game/index.tsx b/apps/farminglog/src/pages/game/index.tsx index 92d41b93..7f55c24d 100644 --- a/apps/farminglog/src/pages/game/index.tsx +++ b/apps/farminglog/src/pages/game/index.tsx @@ -1,17 +1,51 @@ -import React, { useState } from 'react'; // useState를 import 합니다. +import { useEffect, useRef, useState } from 'react'; // useState를 import 합니다. import { UnityWebGL } from '../../components/UnityWebGL'; -import { GameContainer, GameTitle, StartButton, StartContainer } from './index.styled.ts'; +import useMediaQueries from "@/hooks/useMediaQueries"; +import useTallPage from "@/hooks/useTallPage"; +import { GameContainer, StartButton, StartContainer, LandingHero, UpButton, UpButtonImage } from './index.styled.ts'; + const Game: React.FC = () => { const [isGameStarted, setIsGameStarted] = useState(false); - + const { isMobile } = useMediaQueries(); + const landingImage = 'https://farmsystem-bucket.s3.ap-northeast-2.amazonaws.com/game/DetailGameLanding.png'; + const upButtonImage = 'https://farmsystem-bucket.s3.ap-northeast-2.amazonaws.com/game/UpGameButton.png'; + const isTallPage = useTallPage(3000); + const gameContainerRef = useRef(null); + const [isGameContainerInView, setIsGameContainerInView] = useState(false); const handleStartGame = () => { setIsGameStarted(true); }; + const handleScrollTop = () => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + useEffect(() => { + if (isMobile) return; + if (!gameContainerRef.current) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + setIsGameContainerInView(entry.isIntersecting); + }, + { + root: null, + rootMargin: '0px', + threshold: 0.1, + } + ); + + observer.observe(gameContainerRef.current); + return () => observer.disconnect(); + }, [isMobile]); return ( - - 🌱 Grow My Farm +
+ + {/** isGameStarted 값에 따라 조건부로 렌더링. */} {isGameStarted ? ( @@ -27,6 +61,20 @@ const Game: React.FC = () => { )} + + {(!isMobile) && ( + + + )} + {(!isMobile && isTallPage && !isGameContainerInView) && ( + + + )} + +
); };