feat: add @radix-ui/react-dialog dependency and implement dialog component

- Added @radix-ui/react-dialog to dependencies in package.json and bun.lock.
- Updated page.tsx to replace placeholder text with "We are in MAGICALWORLD!".
- Refactored SUPERCOMMAND component to handle key sequences and show dialog on match.
- Implemented Dialog component with header, footer, and description using Radix UI primitives.
This commit is contained in:
암냥 2025-09-27 06:47:20 +09:00
commit a3a53a68ec
5 changed files with 311 additions and 100 deletions

View file

@ -1,114 +1,145 @@
import React from "react";
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
export default function SUPERCOMMAND() {
const [visible, setVisible] = React.useState(false);
const [tabCount, setTabCount] = React.useState(() => {
const saved = localStorage.getItem('tabCount');
return saved ? parseInt(saved, 10) : 0;
});
const [showPressSpace, setShowPressSpace] = React.useState(false);
const audioContextRef = React.useRef<AudioContext | null>(null);
// tabCount가 변경될 때마다 localStorage에 저장
React.useEffect(() => {
localStorage.setItem('tabCount', tabCount.toString());
}, [tabCount]);
// Beep 소리를 생성하는 함수 (Web Audio API 사용)
const playBeep = (count: number) => {
if (!audioContextRef.current) {
audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)();
}
const context = audioContextRef.current;
const oscillator = context.createOscillator();
const gainNode = context.createGain();
oscillator.connect(gainNode);
gainNode.connect(context.destination);
const frequency = 440 + (count - 1); // 콤보 수에 따라 주파수 증가 (440Hz부터 10Hz씩 상승)
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.1, context.currentTime); // 볼륨
gainNode.gain.exponentialRampToValueAtTime(0.001, context.currentTime + 0.5); // 0.5초 후 페이드 아웃
oscillator.start(context.currentTime);
oscillator.stop(context.currentTime + 0.5); // 0.5초로 길게 설정
};
const [keySequence, setKeySequence] = React.useState("");
const [showDialog, setShowDialog] = React.useState(false);
const targetSequence = "MAGICALWORLD";
const handleKeyDown = (event: KeyboardEvent) => {
console.log(event.key)
// Tab 누르면 보이게
if (event.key === "Tab") {
// event.preventDefault();
setVisible(true);
playBeep(tabCount); // Tab 누를 때마다 beep 소리 재생
} else if (event.key !== "Tab" && event.key !== "Control" && event.key !== "Alt" && event.key !== "Meta" && event.key !== "Shift") {
setVisible(false);
setTabCount(0);
setShowPressSpace(false);
}
// 영문자만 처리
if (event.key.length === 1 && /[a-zA-Z]/.test(event.key)) {
console.log(keySequence);
console.log(targetSequence);
console.log("Key pressed:", event.key);
setKeySequence(prev => {
const newSequence = prev + event.key;
console.log("New sequence:", newSequence);
// 'Tab' 키를 누를 때마다 카운트(연속 또는 누적)
if (event.key === "Tab") {
setTabCount((prev) => {
const next = prev + 1;
if (next === 8 || next === 1108) {
setShowPressSpace(true);
} else if (next > 8) {
setShowPressSpace(false);
// 목표 시퀀스와 정확히 일치하는지 확인 (대문자만)
if (newSequence === targetSequence) {
console.log("Sequence matched!");
setShowDialog(true);
return "";
}
return next;
});
}
// 스페이스바 눌렀을 때, 메시지가 보이는 상태이면 페이지 이동
if ((event.code === "Space" || event.key === " ") && showPressSpace) {
// 원하는 이동 경로로 변경하세요:
if (tabCount === 8) {
localStorage.setItem('visited8', 'true'); // 방문 플래그 저장
const redirectList = [
"https://www.youtube.com/watch?v=DjGxGMxvg4M",
"https://www.youtube.com/watch?v=oQcaPVGUtuA",
"https://www.youtube.com/watch?v=E6RQgBwcmG8",
"https://www.youtube.com/watch?v=xUNFDn2my68",
"https://www.youtube.com/watch?v=W-_90c08AIY",
"https://www.youtube.com/watch?v=GyEIHPyIQQg",
"https://www.youtube.com/watch?v=xZi12DkWkHA"
]
window.location.href = redirectList[Math.floor(Math.random() * redirectList.length)];
} else if (tabCount === 1108) {
window.location.href = "https://imnya.ng/whoamiandwhoareyou ";
}
// 목표 시퀀스의 시작 부분과 일치하는지 확인 (대문자만)
if (targetSequence.startsWith(newSequence)) {
return newSequence;
}
// 일치하지 않으면 현재 키부터 다시 시작
const restartSequence = event.key;
if (targetSequence.startsWith(restartSequence)) {
return restartSequence;
}
// 완전히 초기화
return "";
});
} else {
// 특수키나 숫자가 입력되면 시퀀스 초기화
setKeySequence("");
}
};
// const handleKeyUp = (event: KeyboardEvent) => {
// // Tab에서 손을 떼면 숨김
// if (event.key === "Tab") {
// setVisible(false);
// // 필요하면 카운트/메시지 리셋
// // setEightCount(0);
// // setShowPressSpace(false);
// }
// };
React.useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
// window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
// window.removeEventListener("keyup", handleKeyUp);
};
}, [showPressSpace]);
if (!visible) return null;
}, []);
return (
<div id="supercommand" className="fixed bottom-5 left-5">
<h1 className="text-2xl"><span className="text-3xl font-bold">{tabCount}</span> Combo</h1>
{showPressSpace ? <p> .{tabCount === 8 && localStorage.getItem('visited8') ? null : null}</p> : null}
</div>
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogContent className="flex flex-col gap-0 p-0 sm:max-h-[min(640px,80vh)] sm:max-w-lg [&>button:last-child]:top-3.5">
<DialogHeader className="contents space-y-0 text-left">
<DialogTitle className="border-b px-6 py-4 text-base">
???
</DialogTitle>
<div className="overflow-y-auto">
<DialogDescription asChild>
<div className="px-6 py-4">
<div className="[&_strong]:text-foreground space-y-4 [&_strong]:font-semibold pb-32">
<p style={{ whiteSpace: 'pre-line' }}>{`마법소녀는 세상을 구했는데
?
?
?
?
?
믿
?
?
?
?
?
`}
</p>
</div>
</div>
</DialogDescription>
<DialogFooter className="px-6 py-6 sm:justify-start fixed bottom-0 left-0 w-full bg-background/80 backdrop-blur-sm">
<Button type="button">Okay</Button>
</DialogFooter>
</div>
</DialogHeader>
</DialogContent>
</Dialog>
);
}