<p data-ke-size="size16"> </p>
<p data-ke-size="size16"> </p>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/7.22.9/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<div>
<style>
body {
background: linear-gradient(to bottom, #1e3a8a, #7e22ce);
color: white;
font-family: 'Arial', sans-serif;
margin: 0;
padding: 0;
overflow-x: hidden;
}
.app-container {
max-width: 400px;
margin: 0 auto;
min-height: 100vh;
position: relative;
}
.screen {
padding: 20px;
display: none;
flex-direction: column;
justify-content: space-between;
min-height: 100vh;
background: rgba(0, 0, 0, 0.3);
transition: opacity 0.5s ease-in-out;
}
.screen.active {
display: flex;
opacity: 1;
}
.stars {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.star {
position: absolute;
background: white;
border-radius: 50%;
opacity: 0.7;
animation: twinkle 2s infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 0.7; }
50% { opacity: 0.3; }
}
.luna {
position: fixed;
bottom: 80px;
right: 20px;
width: 60px;
height: 60px;
background: url('https://via.placeholder.com/60?text=루나') no-repeat center;
background-size: cover;
cursor: pointer;
animation: float 3s ease-in-out infinite;
z-index: 10;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.nav-bar {
position: fixed;
bottom: 0;
width: 400px;
display: flex;
justify-content: space-around;
padding: 10px 0;
background: rgba(0, 0, 0, 0.7);
z-index: 10;
}
.nav-button {
background: none;
border: none;
color: white;
font-size: 14px;
padding: 10px;
transition: color 0.3s;
}
.nav-button.active {
color: #a78bfa;
font-weight: bold;
}
.button {
background: #7c3aed;
color: white;
padding: 12px;
border-radius: 8px;
text-align: center;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
.button:hover {
background: #6d28d9;
}
.mbti-option {
background: #3b82f6;
padding: 12px;
border-radius: 8px;
text-align: center;
cursor: pointer;
transition: background 0.3s;
}
.mbti-option:hover {
background: #2563eb;
}
.progress-bar {
width: 100%;
height: 8px;
background: #4b5563;
border-radius: 4px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: #a78bfa;
transition: width 0.3s;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 20;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: #1e40af;
padding: 20px;
border-radius: 12px;
max-width: 90%;
text-align: center;
}
.apple-game {
flex-grow: 1;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
position: relative;
overflow: hidden;
}
.apple {
position: absolute;
width: 30px;
height: 30px;
background: url('https://via.placeholder.com/30?text=사과') no-repeat center;
background-size: cover;
cursor: pointer;
}
.mission-item {
background: #1e40af;
padding: 10px;
border-radius: 8px;
margin-bottom: 10px;
}
.mission-button {
background: #16a34a;
color: white;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
}
.avatar-container {
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.2);
border-radius: 8px;
margin-bottom: 10px;
}
.avatar-figure {
width: 100px;
height: 150px;
background: #6b7280;
border-radius: 20px;
position: relative;
}
.avatar-head {
width: 50px;
height: 50px;
background: #d1d5db;
border-radius: 50%;
position: absolute;
top: 10px;
left: 25px;
}
.shop-items {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.shop-item {
background: #1e40af;
padding: 10px;
border-radius: 8px;
text-align: center;
}
.community-post {
background: #1e40af;
padding: 10px;
border-radius: 8px;
margin-bottom: 10px;
}
.hexagon-chart {
width: 100%;
height: 150px;
background: url('https://via.placeholder.com/300x150?text=헥사곤') no-repeat center;
background-size: contain;
}
</style>
</div>
<div id="root"> </div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
// 별 생성 컴포넌트
const Stars = () => {
const stars = [];
for (let i = 0; i < 100; i++) {
const style = {
left: `${Math.random() * 100}%`,
top: `${Math.random() * 100}%`,
animationDelay: `${Math.random() * 3}s`,
width: `${Math.random() * 3 + 1}px`,
height: `${Math.random() * 3 + 1}px`,
};
stars.push(<div key={i} className="star" style={style}></div>);
}
return <div className="stars">{stars}</div>;
};
// 루나 캐릭터
const Luna = ({ onClick }) => {
return (
<div className="luna" onClick={onClick} title="루나와 대화하기" role="button" aria-label="루나와 대화"></div>
);
};
// 내비게이션 바
const NavBar = ({ currentScreen, setCurrentScreen }) => {
const navItems = [
{ id: 'home', label: '홈' },
{ id: 'missions', label: '미션' },
{ id: 'avatar', label: '아바타' },
{ id: 'shop', label: '상점' },
{ id: 'community', label: '커뮤니티' },
];
return (
<div className="nav-bar" role="navigation">
{navItems.map((item) => (
<button
key={item.id}
className={`nav-button ${currentScreen === item.id ? 'active' : ''}`}
onClick={() => setCurrentScreen(item.id)}
aria-label={item.label}
>
{item.label}
</button>
))}
</div>
);
};
// 모달 컴포넌트
const Modal = ({ isOpen, onClose, title, children }) => {
return (
<div className={`modal ${isOpen ? 'active' : ''}`} onClick={onClose} role="dialog" aria-modal="true">
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<h3 className="text-xl font-bold mb-4">{title}</h3>
{children}
<button className="button" onClick={onClose} aria-label="모달 닫기">닫기</button>
</div>
</div>
);
};
// 사과 자르기 미니게임
const AppleGame = ({ onComplete }) => {
const [apples, setApples] = useState([]);
const [score, setScore] = useState(0);
const [timeLeft, setTimeLeft] = useState(15);
const [gameStarted, setGameStarted] = useState(false);
const [gameEnded, setGameEnded] = useState(false);
const gameAreaRef = useRef(null);
const startGame = () => {
setGameStarted(true);
setScore(0);
setTimeLeft(15);
setApples([]);
setGameEnded(false);
};
const cutApple = (index) => {
setApples(apples.filter((_, i) => i !== index));
setScore(score + 1);
};
useEffect(() => {
if (!gameStarted || gameEnded) return;
const interval = setInterval(() => {
if (gameAreaRef.current) {
const width = gameAreaRef.current.offsetWidth - 30;
const height = gameAreaRef.current.offsetHeight - 30;
setApples(prev => [
...prev,
{
id: Date.now(),
x: Math.random() * width,
y: Math.random() * height,
},
]);
}
}, 1000);
const timer = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
clearInterval(interval);
clearInterval(timer);
setGameEnded(true);
onComplete(score);
return 0;
}
return prev - 1;
});
}, 1000);
return () => {
clearInterval(interval);
clearInterval(timer);
};
}, [gameStarted, gameEnded, score]);
if (!gameStarted) {
return (
<div className="flex flex-col items-center justify-center h-full">
<h3 className="text-xl mb-4">민첩성 테스트: 사과 자르기</h3>
<p className="mb-4 text-sm">화면에 나타나는 사과를 빠르게 터치하세요!</p>
<button className="button" onClick={startGame}>시작하기</button>
</div>
);
}
if (gameEnded) {
return (
<div className="flex flex-col items-center justify-center h-full">
<h3 className="text-xl mb-4">테스트 완료!</h3>
<p className="mb-4">점수: {score} 점</p>
<button className="button" onClick={startGame}>다시 하기</button>
</div>
);
}
return (
<div className="flex flex-col h-full">
<div className="flex justify-between mb-4">
<p>점수: {score}</p>
<p>시간: {timeLeft}초</p>
</div>
<div className="apple-game" ref={gameAreaRef}>
{apples.map((apple, index) => (
<div
key={apple.id}
className="apple"
style={{ left: apple.x + 'px', top: apple.y + 'px' }}
onClick={() => cutApple(index)}
role="button"
aria-label="사과 자르기"
/>
))}
</div>
</div>
);
};
// 메인 앱 컴포넌트
const App = () => {
const [currentScreen, setCurrentScreen] = useState('welcome');
const [mbtiQuestion, setMbtiQuestion] = useState(1);
const [mbtiResult, setMbtiResult] = useState(null);
const [dreamCountry, setDreamCountry] = useState(null);
const [coins, setCoins] = useState(0);
const [lunaModalOpen, setLunaModalOpen] = useState(false);
const [gameResult, setGameResult] = useState(null);
const totalMbtiQuestions = 20;
const handleAppleGameComplete = (score) => {
const earnedCoins = score * 5;
setCoins(prev => prev + earnedCoins);
setGameResult({
score,
coins: earnedCoins,
});
};
const answerMbti = (answer) => {
if (mbtiQuestion < totalMbtiQuestions) {
setMbtiQuestion(mbtiQuestion + 1);
} else {
const results = [
'깊은 몰입형 (Deep Diver)',
'예민한 감각형 (Light Sensor)',
'불규칙한 변동형 (Rhythm Seeker)',
'안정적 규칙형 (Stable Sleeper)',
];
setMbtiResult(results[Math.floor(Math.random() * results.length)]);
setCurrentScreen('mbtiResult');
}
};
const selectDreamCountry = (country) => {
setDreamCountry(country);
setCurrentScreen('home');
};
const screens = {
welcome: (
<div className="screen active flex flex-col items-center justify-center text-center">
<Stars />
<h1 className="text-3xl font-bold mb-6">허니드림</h1>
<p className="text-lg mb-8">더 나은 수면으로 꿈의 나라를 만들어보세요!</p>
<div className="w-48 h-48 bg-gray-700 rounded-full mb-8 flex items-center justify-center animate-pulse">
<div className="w-40 h-40 bg-blue-900 rounded-full flex items-center justify-center">
<div className="w-32 h-32 bg-indigo-800 rounded-full flex items-center justify-center">
<div className="w-24 h-24 bg-purple-800 rounded-full"></div>
</div>
</div>
</div>
<button
className="button"
onClick={() => setCurrentScreen('mbti')}
aria-label="게임 시작"
>
시작하기
</button>
{/* 주석: 타겟(20~40대 직장인/학생)을 위해 간단한 CTA로 빠른 온보딩 유도, 몽환적 애니메이션으로 호기심 자극 */}
</div>
),
mbti: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">수면 MBTI 테스트</h2>
<p className="text-sm mb-2">질문 {mbtiQuestion}/{totalMbtiQuestions}</p>
<p className="mb-6">
{mbtiQuestion === 1 && "정해진 시간에 자고 일어나는 것이 편하다."}
{mbtiQuestion === 2 && "수면 중에 자주 깨는 편이다."}
{mbtiQuestion === 3 && "스트레스를 받으면 수면에 영향이 크다."}
{mbtiQuestion > 3 && `질문 ${mbtiQuestion}`}
</p>
<div className="flex flex-col space-y-4 mb-6">
<div className="mbti-option" onClick={() => answerMbti('A')} role="button" aria-label="매우 그렇다">매우 그렇다</div>
<div className="mbti-option" onClick={() => answerMbti('B')} role="button" aria-label="약간 그렇다">약간 그렇다</div>
<div className="mbti-option" onClick={() => answerMbti('C')} role="button" aria-label="별로 그렇지 않다">별로 그렇지 않다</div>
<div className="mbti-option" onClick={() => answerMbti('D')} role="button" aria-label="전혀 그렇지 않다">전혀 그렇지 않다</div>
</div>
<div className="progress-bar">
<div
className="progress-bar-fill"
style={{ width: `${(mbtiQuestion / totalMbtiQuestions) * 100}%` }}
></div>
</div>
{/* 주석: 타겟의 개인화 니즈 충족, 3~5분 내 완료 가능한 간결한 UX로 바쁜 사용자 부담 감소 */}
</div>
),
mbtiResult: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">당신의 수면 유형</h2>
<div className="bg-indigo-900 p-6 rounded-lg text-center mb-8">
<h3 className="text-xl font-bold mb-4">{mbtiResult || "결과 계산 중"}</h3>
<p className="mb-4">
{mbtiResult === '깊은 몰입형 (Deep Diver)' && '한번 잠에 들면 깊게 잠드는 타입입니다. 일정한 수면 패턴을 유지하세요.'}
{mbtiResult === '예민한 감각형 (Light Sensor)' && '환경 변화에 민감합니다. 수면 환경 최적화가 중요해요.'}
{mbtiResult === '불규칙한 변동형 (Rhythm Seeker)' && '수면 패턴이 불규칙합니다. 규칙적인 취침 시간을 설정하세요.'}
{mbtiResult === '안정적 규칙형 (Stable Sleeper)' && '규칙적인 수면 습관을 유지하며 수면 질을 높이세요.'}
</p>
</div>
<h3 className="text-xl font-bold mb-2">꿈의 나라 선택</h3>
<p className="text-sm mb-4">궁극적으로 도달하고 싶은 이상향을 선택하세요.</p>
<div className="grid grid-cols-2 gap-4">
<div className="bg-blue-900 p-4 rounded-lg text-center cursor-pointer" onClick={() => selectDreamCountry('island')}>
<p className="font-bold mb-2">열대 휴양지</p>
<div className="h-16 bg-blue-800 rounded flex items-center justify-center">이미지</div>
</div>
<div className="bg-green-900 p-4 rounded-lg text-center cursor-pointer" onClick={() => selectDreamCountry('forest')}>
<p className="font-bold mb-2">신비한 숲</p>
<div className="h-16 bg-green-800 rounded flex items-center justify-center">이미지</div>
</div>
<div className="bg-gray-800 p-4 rounded-lg text-center cursor-pointer" onClick={() => selectDreamCountry('city')}>
<p className="font-bold mb-2">현대 도시</p>
<div className="h-16 bg-gray-700 rounded flex items-center justify-center">이미지</div>
</div>
<div className="bg-indigo-900 p-4 rounded-lg text-center cursor-pointer" onClick={() => selectDreamCountry('cosmos')}>
<p className="font-bold mb-2">우주 정거장</p>
<div className="h-16 bg-indigo-800 rounded flex items-center justify-center">이미지</div>
</div>
</div>
{/* 주석: 타겟의 장기 목표(꿈의 나라) 설정, 개인화로 몰입감 강화 */}
</div>
),
home: (
<div className="screen active flex flex-col h-full">
<Stars />
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold">허니드림</h2>
<div className="bg-purple-900 px-3 py-1 rounded-full text-sm">
코인: {coins}
</div>
</div>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">오늘의 수면 상태</h3>
<p className="text-sm">아직 오늘의 수면을 기록하지 않았습니다.</p>
<button
className="button mt-2 w-full"
onClick={() => setCurrentScreen('sleep')}
aria-label="취침 준비"
>
취침 준비
</button>
</div>
<div className="bg-purple-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">능력치 현황</h3>
<div className="hexagon-chart"></div>
</div>
<div className="bg-blue-900 p-4 rounded-lg">
<h3 className="font-bold mb-2">오늘의 미션</h3>
{['10분 햇빛 쬐기', '5분 명상'].map((mission, i) => (
<div key={i} className="mission-item">
<p>{mission}</p>
<div className="flex justify-between items-center mt-1">
<span>+15 코인</span>
<button className="mission-button">완료하기</button>
</div>
</div>
))}
<button
className="button mt-4 w-full"
onClick={() => setCurrentScreen('missions')}
aria-label="모든 미션 보기"
>
모든 미션 보기
</button>
</div>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 바쁜 일정 고려, 핵심 CTA(취침, 미션) 강조로 빠른 접근 제공 */}
</div>
),
sleep: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">취침 준비</h2>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">수면 시간 설정</h3>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<p className="text-sm mb-1">취침 시간</p>
<select className="w-full bg-indigo-800 p-2 rounded" aria-label="취침 시간 선택">
{Array.from({ length: 24 }).map((_, i) => (
<option key={i} value={i}>{i}:00</option>
))}
</select>
</div>
<div>
<p className="text-sm mb-1">기상 시간</p>
<select className="w-full bg-indigo-800 p-2 rounded" aria-label="기상 시간 선택">
{Array.from({ length: 24 }).map((_, i) => (
<option key={i} value={i}>{i}:00</option>
))}
</select>
</div>
</div>
</div>
<div className="bg-purple-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">수면 환경 체크리스트</h3>
{['방을 어둡게 했나요?', '실내 온도는 적절한가요?', '전자기기를 치웠나요?'].map((item, i) => (
<div key={i} className="flex items-center mt-2">
<input type="checkbox" id={`check-${i}`} className="mr-2" aria-label={item} />
<label htmlFor={`check-${i}`} className="text-sm">{item}</label>
</div>
))}
</div>
<button
className="button mb-4"
onClick={() => setCurrentScreen('morning')}
aria-label="취침 모드 시작"
>
취침 모드 시작하기
</button>
<button
className="bg-gray-600 text-white px-4 py-2 rounded-lg"
onClick={() => setCurrentScreen('home')}
aria-label="홈으로 돌아가기"
>
돌아가기
</button>
{/* 주석: 타겟의 비웨어러블 사용자 고려, 간단한 입력으로 수면 모드 활성화 */}
</div>
),
morning: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">아침 평가</h2>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">당신의 수면은 어땠나요?</h3>
<div className="flex justify-between my-2">
<span>매우 나쁨</span>
<span>매우 좋음</span>
</div>
<input type="range" min="1" max="10" className="w-full" aria-label="수면 품질 평가" />
<h4 className="font-bold mt-4 mb-2">기타 체크리스트</h4>
{['중간에 깬 적이 있나요?', '꿈을 꿨나요?', '아침에 개운한가요?'].map((item, i) => (
<div key={i} className="flex items-center mt-2">
<input type="checkbox" id={`morning-check-${i}`} className="mr-2" aria-label={item} />
<label htmlFor={`morning-check-${i}`} className="text-sm">{item}</label>
</div>
))}
</div>
<button
className="button mb-4"
onClick={() => {
setCoins(prev => prev + 50);
setCurrentScreen('minigame');
}}
aria-label="평가 완료"
>
평가 완료 (+50 코인)
</button>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 수면 이점 체감 니즈 충족, 간단한 평가로 빠른 보상 제공 */}
</div>
),
minigame: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">미니게임</h2>
<p className="mb-4 text-sm">아침 활력을 위한 미니게임을 해보세요!</p>
<AppleGame onComplete={handleAppleGameComplete} />
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 재미 니즈 충족, 15초 내 완료 가능한 게임으로 몰입감 제공 */}
</div>
),
missions: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">일일 미션</h2>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">기본 미션</h3>
{[
'물 2L 마시기',
'10분 햇빛 쬐기',
'수면 일기 작성하기',
].map((mission, i) => (
<div key={i} className="mission-item">
<p>{mission}</p>
<div className="flex justify-between items-center mt-2">
<span>+15 코인</span>
<button className="mission-button" aria-label={`${mission} 완료`}>완료하기</button>
</div>
</div>
))}
</div>
<div className="bg-purple-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">특별 미션</h3>
{[
'3일 연속 같은 시간에 일어나기',
'취침 전 30분 스마트폰 사용 안하기',
].map((mission, i) => (
<div key={i} className="mission-item">
<p>{mission}</p>
<div className="flex justify-between items-center mt-2">
<span>+30 코인</span>
<button className="mission-button" aria-label={`${mission} 도전`}>도전하기</button>
</div>
</div>
))}
</div>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 짧은 시간 투자 니즈 충족, 3~10분 미션으로 습관 개선 유도 */}
</div>
),
avatar: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">나의 드림 아바타</h2>
<div className="avatar-container">
<div className="avatar-figure">
<div className="avatar-head"></div>
</div>
</div>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<h3 className="font-bold mb-2">아바타 커스터마이징</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<p className="text-sm mb-1">머리 스타일</p>
<select className="w-full bg-indigo-800 p-2 rounded" aria-label="머리 스타일 선택">
<option>기본형</option>
<option>단발</option>
<option>긴 머리</option>
</select>
</div>
<div>
<p className="text-sm mb-1">옷 스타일</p>
<select className="w-full bg-indigo-800 p-2 rounded" aria-label="옷 스타일 선택">
<option>기본 옷</option>
<option>파자마</option>
<option>정장</option>
</select>
</div>
</div>
<button className="button mt-4 w-full" aria-label="아바타 저장">저장하기</button>
</div>
<div className="bg-purple-900 p-4 rounded-lg">
<h3 className="font-bold mb-2">드림월드 꾸미기</h3>
<p className="text-sm mb-2">꿈의 나라: {dreamCountry || '미선택'}</p>
<button className="button w-full" aria-label="꿈의 나라 방문">꿈의 나라 방문하기</button>
</div>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 창의적 표현 니즈 충족, 꿈의 나라로 장기 목표 동기 부여 */}
</div>
),
shop: (
<div className="screen active flex flex-col h-full">
<Stars />
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold">아이템 상점</h2>
<div className="bg-purple-900 px-3 py-1 rounded-full text-sm">
코인: {coins}
</div>
</div>
<div className="shop-items">
{[
{ name: '푹신한 베개', price: 100, desc: '수면의 질 +5%' },
{ name: '수면 안대', price: 50, desc: '깊은 수면 +10%' },
{ name: '아로마 디퓨저', price: 150, desc: '수면 환경 +15%' },
{ name: '수면 음악', price: 80, desc: '빠른 입면 +20%' },
].map((item, i) => (
<div key={i} className="shop-item">
<div className="w-full h-24 bg-gray-800 rounded mb-2"></div>
<h4 className="font-bold">{item.name}</h4>
<p className="text-xs mb-2">{item.desc}</p>
<div className="flex justify-between items-center">
<span>{item.price} 코인</span>
<button className="bg-purple-700 text-white px-2 py-1 rounded text-xs" aria-label={`${item.name} 구매`}>
구매하기
</button>
</div>
</div>
))}
</div>
<h3 className="font-bold mt-6 mb-2">프리미엄 아이템</h3>
<div className="shop-items">
{[
{ name: '꿈 분석기', price: 500, desc: '꿈 해석 기능 추가' },
{ name: '명상 가이드', price: 300, desc: '수면 명상 프로그램' },
].map((item, i) => (
<div key={i} className="shop-item">
<div className="w-full h-24 bg-indigo-800 rounded mb-2"></div>
<h4 className="font-bold">{item.name}</h4>
<p className="text-xs mb-2">{item.desc}</p>
<div className="flex justify-between items-center">
<span>{item.price} 코인</span>
<button className="bg-indigo-600 text-white px-2 py-1 rounded text-xs" aria-label={`${item.name} 구매`}>
구매하기
</button>
</div>
</div>
))}
</div>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 실용적 보상 니즈 충족, 코인으로 수면 관련 아이템 구매 가능 */}
</div>
),
community: (
<div className="screen active flex flex-col h-full">
<Stars />
<h2 className="text-2xl font-bold mb-4">수면 커뮤니티</h2>
<div className="bg-indigo-900 p-4 rounded-lg mb-4">
<div className="flex justify-between items-center mb-2">
<h3 className="font-bold">수면 팁 공유</h3>
<button className="bg-blue-600 text-white px-2 py-1 rounded text-xs" aria-label="새 글 작성">
글쓰기
</button>
</div>
{[
{ user: '꿀잠러', title: '카페인 줄이고 수면 개선했어요', likes: 24 },
{ user: '새벽별', title: '숙면을 위한 침실 온도 꿀팁', likes: 15 },
].map((post, i) => (
<div key={i} className="community-post">
<h4 className="font-bold">{post.title}</h4>
<div className="flex justify-between items-center mt-2 text-sm">
<span>{post.user}</span>
<div className="flex items-center">
<span className="mr-1">좋아요 {post.likes}</span>
<button className="bg-purple-700 text-white px-2 py-1 rounded text-xs" aria-label="좋아요">
👍
</button>
</div>
</div>
</div>
))}
</div>
<div className="bg-purple-900 p-4 rounded-lg">
<h3 className="font-bold mb-2">수면 챌린지</h3>
<p className="text-sm mb-4">친구들과 함께 건강한 수면 습관을 만들어보세요!</p>
<div className="bg-purple-800 p-3 rounded mb-3">
<h4 className="font-bold">7일 연속 같은 시간에 일어나기</h4>
<div className="flex justify-between items-center mt-2">
<span>참여자: 126명</span>
<button className="bg-green-600 text-white px-2 py-1 rounded text-xs" aria-label="챌린지 참여">
참여하기
</button>
</div>
</div>
<div className="bg-purple-800 p-3 rounded">
<h4 className="font-bold">30일 취침 전 스크린 끄기</h4>
<div className="flex justify-between items-center mt-2">
<span>참여자: 87명</span>
<button className="bg-green-600 text-white px-2 py-1 rounded text-xs" aria-label="챌린지 참여">
참여하기
</button>
</div>
</div>
</div>
<NavBar currentScreen={currentScreen} setCurrentScreen={setCurrentScreen} />
<Luna onClick={() => setLunaModalOpen(true)} />
{/* 주석: 타겟의 소셜 연결 니즈 충족, 커뮤니티로 소속감과 동기 부여 */}
</div>
),
};
return (
<div className="app-container">
{screens[currentScreen]}
{['home', 'missions', 'avatar', 'shop', 'community', 'morning', 'minigame'].includes(currentScreen) && (
<Luna onClick={() => setLunaModalOpen(true)} />
)}
<Modal
isOpen={lunaModalOpen}
onClose={() => setLunaModalOpen(false)}
title="루나와의 대화"
>
<div className="mb-4 p-3 bg-indigo-900 rounded-lg">
<p>안녕하세요! 저는 당신의 수면 도우미 루나예요. 어떻게 도와드릴까요?</p>
</div>
<div className="flex mb-4">
<input
type="text"
placeholder="루나에게 질문하기..."
className="flex-grow p-2 rounded-l bg-gray-800 text-white"
aria-label="루나에게 질문"
/>
<button className="bg-purple-700 text-white px-4 py-2 rounded-r" aria-label="질문 전송">
전송
</button>
</div>
<div className="text-sm">
<p>루나에게 수면에 관한 질문을 해보세요!</p>
<ul className="list-disc pl-5 mt-2">
<li>수면의 질을 높이는 방법이 궁금해요</li>
<li>불면증에 좋은 방법이 있을까요?</li>
<li>꿈을 더 생생하게 꾸고 싶어요</li>
</ul>
</div>
</Modal>
<Modal
isOpen={gameResult !== null}
onClose={() => setGameResult(null)}
title="게임 결과"
>
<div className="text-center mb-4">
<h3 className="text-xl mb-2">축하합니다!</h3>
<p>점수: {gameResult?.score} 점</p>
<p>획득한 코인: {gameResult?.coins} 코인</p>
</div>
<button
className="button w-full"
onClick={() => {
setGameResult(null);
setCurrentScreen('home');
}}
aria-label="홈으로 돌아가기"
>
홈으로 돌아가기
</button>
</Modal>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
</script>
<p data-ke-size="size16"> </p>
<p data-ke-size="size16"> </p>
카테고리 없음