import React, { useState, useEffect, useRef, useCallback } from 'react';
import { MousePointer2, Fish, Music, Home, Settings, Battery, Wifi } from 'lucide-react';
// --- 유틸리티 및 오디오 ---
const playTone = (frequency) => {
try {
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
// 고양이 울음소리와 비슷하게 부드러운 사인파 사용
oscillator.type = 'sine';
oscillator.frequency.value = frequency;
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);
oscillator.start();
// 소리가 서서히 줄어들도록 설정 (야옹~ 하는 느낌)
gainNode.gain.setValueAtTime(1, audioCtx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.5);
setTimeout(() => {
oscillator.stop();
audioCtx.close();
}, 500);
} catch (e) {
console.log("Audio not supported or interaction required first.");
}
};
// --- 앱 컴포넌트들 ---
// 1. 레이저 포인터 앱
const LaserApp = () => {
const [position, setPosition] = useState({ x: 50, y: 50 });
const [score, setScore] = useState(0);
const containerRef = useRef(null);
const moveLaser = useCallback(() => {
setPosition({
x: Math.random() * 90 + 5, // 5% ~ 95%
y: Math.random() * 90 + 5,
});
}, []);
useEffect(() => {
const interval = setInterval(moveLaser, 1200); // 1.2초마다 이동
return () => clearInterval(interval);
}, [moveLaser]);
const handleCatch = (e) => {
e.stopPropagation(); // 부모 터치 이벤트 방지
setScore(s => s + 1);
moveLaser(); // 잡으면 바로 도망감
playTone(800); // 삑! 소리
};
return (
잡은 횟수: {score}
{/* 레이저 점 */}
);
};
// 2. 물고기 연못 앱
const FishApp = () => {
const [fishes, setFishes] = useState([]);
const [ripples, setRipples] = useState([]);
useEffect(() => {
// 초기 물고기 생성
const initialFishes = Array.from({ length: 5 }).map((_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
speed: Math.random() * 0.5 + 0.2,
direction: Math.random() > 0.5 ? 1 : -1, // 1: 오른쪽, -1: 왼쪽
size: Math.random() * 20 + 40 // 40px ~ 60px
}));
setFishes(initialFishes);
let animationFrame;
const animate = () => {
setFishes(prevFishes => prevFishes.map(fish => {
let newX = fish.x + (fish.speed * fish.direction);
let newDirection = fish.direction;
// 화면 밖으로 나가면 방향 전환
if (newX > 110) newDirection = -1;
if (newX < -10) newDirection = 1;
return { ...fish, x: newX, direction: newDirection };
}));
animationFrame = requestAnimationFrame(animate);
};
animationFrame = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationFrame);
}, []);
const handleTap = (e) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = (e.clientX || (e.touches && e.touches[0].clientX)) - rect.left;
const y = (e.clientY || (e.touches && e.touches[0].clientY)) - rect.top;
const newRipple = { id: Date.now(), x, y };
setRipples(prev => [...prev, newRipple]);
// 물장구 소리 (낮은 주파수)
playTone(200);
// 1초 후 파문 제거
setTimeout(() => {
setRipples(prev => prev.filter(r => r.id !== newRipple.id));
}, 1000);
};
return (
{/* 파문 효과 */}
{ripples.map(ripple => (
))}
{/* 물고기들 */}
{fishes.map(fish => (
🐟
))}
);
};
// 3. 야옹 피아노 앱
const PianoApp = () => {
const keys = [
{ color: 'bg-rose-400', freq: 261.63, label: '도' }, // C4
{ color: 'bg-orange-400', freq: 293.66, label: '레' }, // D4
{ color: 'bg-yellow-400', freq: 329.63, label: '미' }, // E4
{ color: 'bg-green-400', freq: 349.23, label: '파' }, // F4
{ color: 'bg-blue-400', freq: 392.00, label: '솔' }, // G4
];
const [activeKey, setActiveKey] = useState(null);
const handlePress = (index, freq) => {
setActiveKey(index);
playTone(freq);
setTimeout(() => setActiveKey(null), 200);
};
return (
{keys.map((key, index) => (
handlePress(index, key.freq)}
onTouchStart={() => handlePress(index, key.freq)}
>
{activeKey === index && (
야옹!
)}
{key.label}
))}
);
};
// --- 메인 운영체제 컴포넌트 ---
export default function App() {
const [isLocked, setIsLocked] = useState(true);
const [currentApp, setCurrentApp] = useState(null); // null = 바탕화면
const [time, setTime] = useState(new Date());
// 시계 업데이트
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
const apps = [
{ id: 'laser', name: '레이저 잡기', icon: , bgColor: 'bg-slate-800' },
{ id: 'fish', name: '연못 낚시', icon: , bgColor: 'bg-blue-900' },
{ id: 'piano', name: '야옹 피아노', icon: , bgColor: 'bg-purple-900' },
];
// 잠금화면
if (isLocked) {
return (
Nyang OS
{time.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })}
발바닥을 터치하여 잠금해제
);
}
return (
{/* 상단 상태 표시줄 */}
Nyang OS
{time.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit' })}
{/* 메인 콘텐츠 영역 */}
{/* 바탕화면 */}
{!currentApp && (
{apps.map(app => (
))}
)}
{/* 앱 실행 화면 */}
{currentApp === 'laser' &&
}
{currentApp === 'fish' &&
}
{currentApp === 'piano' &&
}
{/* 하단 네비게이션 바 (집사용 - 고양이가 잘 안누르게 작게 배치) */}
);
}