<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>오늘의 룬 운세 - Mystic Runes</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- React & Babel -->
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');
body { font-family: 'Noto Sans KR', sans-serif; }
/* 3D Flip Animation Styles */
.perspective { perspective: 1000px; }
.preserve-3d { transform-style: preserve-3d; }
.backface-hidden { backface-visibility: hidden; }
.rotate-y-180 { transform: rotateY(180deg); }
.animate-bounce-slow {
animation: bounce 3s infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(-5%); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); }
50% { transform: translateY(0); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); }
}
</style>
</head>
<body class="bg-slate-950 text-slate-100">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect } = React;
// ----------------------------------------------------------------------
// RUNE DATA
// ----------------------------------------------------------------------
const RUNE_DATA = [
{ id: 1, name: "Fehu", symbol: "ᚠ", meaning: "부, 소유, 새로운 시작", desc: "물질적인 이득이나 새로운 기회가 찾아올 수 있습니다." },
{ id: 2, name: "Uruz", symbol: "ᚢ", meaning: "힘, 건강, 용기", desc: "강인한 생명력과 변화를 받아들이는 용기가 필요한 날입니다." },
{ id: 3, name: "Thurisaz", symbol: "ᚦ", meaning: "보호, 가시, 경고", desc: "장애물이 있을 수 있으나, 신중하게 행동하면 극복할 수 있습니다." },
{ id: 4, name: "Ansuz", symbol: "ᚫ", meaning: "지혜, 소통, 신의 메시지", desc: "중요한 소식이나 조언을 듣게 될 수 있습니다." },
{ id: 5, name: "Raido", symbol: "ᚱ", meaning: "여행, 이동, 올바른 흐름", desc: "상황의 흐름에 몸을 맡기는 것이 좋습니다." },
{ id: 6, name: "Kaunaz", symbol: "ᚲ", meaning: "불, 깨달음, 열정", desc: "창의적인 아이디어나 해결책이 떠오를 것입니다." },
{ id: 7, name: "Gebo", symbol: "ᚷ", meaning: "선물, 파트너십, 균형", desc: "주고받는 관계의 중요성을 상징합니다." },
{ id: 8, name: "Wunjo", symbol: "ᚹ", meaning: "기쁨, 조화, 성취", desc: "노력의 결실을 맺고 즐거움을 누리는 시기입니다." },
{ id: 9, name: "Hagalaz", symbol: "ᚺ", meaning: "우박, 급격한 변화, 시련", desc: "통제할 수 없는 외부 요인으로 인한 변화가 있을 수 있습니다." },
{ id: 10, name: "Nauthiz", symbol: "ᚾ", meaning: "필요, 인내, 결핍", desc: "인내심을 가지고 시련을 견뎌내야 합니다." },
{ id: 11, name: "Isa", symbol: "ᛁ", meaning: "얼음, 정지, 자아 성찰", desc: "잠시 멈춰서 상황을 재점검할 때입니다." },
{ id: 12, name: "Jera", symbol: "ᛃ", meaning: "수확, 보상", desc: "꾸준히 노력해온 일에 대한 정당한 보상을 받게 됩니다." },
{ id: 13, name: "Eihwaz", symbol: "ᛇ", meaning: "죽음과 재생, 보호", desc: "위기가 기회가 될 수 있습니다." },
{ id: 14, name: "Perthro", symbol: "ᛈ", meaning: "비밀, 운명, 우연", desc: "감춰진 것이 드러나거나 뜻밖의 행운이 찾아옵니다." },
{ id: 15, name: "Algiz", symbol: "ᛉ", meaning: "보호, 본능", desc: "당신을 지켜주는 강력한 힘이 있습니다." },
{ id: 16, name: "Sowilo", symbol: "ᛋ", meaning: "태양, 성공, 활력", desc: "목표를 달성할 수 있는 강력한 에너지가 함께합니다." },
{ id: 17, name: "Tiwaz", symbol: "ᛏ", meaning: "전사, 정의, 희생", desc: "대의를 위해 결단력이 필요한 시기입니다." },
{ id: 18, name: "Berkano", symbol: "ᛒ", meaning: "성장, 새로운 시작", desc: "새로운 프로젝트나 관계가 싹트기 좋은 날입니다." },
{ id: 19, name: "Ehwaz", symbol: "ᛖ", meaning: "협력, 이동", desc: "신뢰를 바탕으로 한 협력이 중요합니다." },
{ id: 20, name: "Mannaz", symbol: "ᛗ", meaning: "인간, 자아", desc: "나 자신을 먼저 알고 타인을 이해하는 것이 중요합니다." },
{ id: 21, name: "Laguz", symbol: "ᛚ", meaning: "물, 흐름, 직관", desc: "논리보다는 감정과 직관의 흐름을 따라가세요." },
{ id: 22, name: "Ingwaz", symbol: "ᛝ", meaning: "풍요, 완성, 휴식", desc: "하나의 주기가 완성되고 평화로운 휴식을 취할 때입니다." },
{ id: 23, name: "Othala", symbol: "ᛟ", meaning: "유산, 전통", desc: "뿌리, 또는 오래된 가치와 관련된 일이 중요해집니다." },
{ id: 24, name: "Dagaz", symbol: "ᛞ", meaning: "낮, 깨달음, 돌파구", desc: "어둠이 걷히고 명확한 해결책이 보입니다." },
];
const getBirthRune = (month, day) => {
if (month === 1 && day >= 13 && day <= 27) return RUNE_DATA.find(r => r.name === "Perthro");
return RUNE_DATA[13]; // Fallback to Perthro for Jan 17
};
// ----------------------------------------------------------------------
// COMPONENTS
// ----------------------------------------------------------------------
const RuneCard = ({ symbol, name, meaning, desc, isRevealed, onClick, isLarge = false }) => {
return (
<div
onClick={onClick}
className={`relative cursor-pointer transition-all duration-700 preserve-3d perspective ${isLarge ? 'w-64 h-96' : 'w-32 h-48'} ${isRevealed ? 'rotate-y-180' : ''}`}
>
{/* Front (Card Back) */}
<div className="absolute inset-0 w-full h-full backface-hidden rounded-xl shadow-xl border-2 border-indigo-300/30 bg-slate-800 flex items-center justify-center overflow-hidden">
<div className="absolute inset-2 border border-indigo-500/20 rounded-lg flex items-center justify-center">
<i data-lucide="star" className="text-indigo-400 animate-pulse w-8 h-8"></i>
</div>
</div>
{/* Back (Rune Face) */}
<div className="absolute inset-0 w-full h-full backface-hidden rounded-xl shadow-2xl border-2 border-amber-500/50 bg-slate-900 flex flex-col items-center justify-center text-center p-4 rotate-y-180">
<div className="text-amber-100 font-serif mb-2 text-opacity-80 text-sm tracking-widest uppercase">{name}</div>
<div className={`${isLarge ? 'text-8xl' : 'text-5xl'} text-amber-400 font-bold drop-shadow-[0_0_15px_rgba(251,191,36,0.5)] mb-4`}>
{symbol}
</div>
{isLarge && (
<div className="mt-4">
<h3 className="text-amber-200 font-bold text-lg mb-1">{meaning}</h3>
<p className="text-slate-300 text-sm leading-relaxed">{desc}</p>
</div>
)}
</div>
</div>
);
};
function App() {
const userBirthDate = { year: 1972, month: 1, day: 17, time: "05:00" };
const birthRune = getBirthRune(userBirthDate.month, userBirthDate.day);
const [dailyRune, setDailyRune] = useState(null);
const [isRevealing, setIsRevealing] = useState(false);
const [hasDrawn, setHasDrawn] = useState(false);
useEffect(() => { lucide.createIcons(); }, [hasDrawn, isRevealing]);
const drawRune = () => {
if (isRevealing) return;
setIsRevealing(true);
setTimeout(() => {
const randomIndex = Math.floor(Math.random() * RUNE_DATA.length);
setDailyRune(RUNE_DATA[randomIndex]);
setHasDrawn(true);
setIsRevealing(false);
}, 800);
};
return (
<div className="min-h-screen bg-slate-950 text-slate-100 pb-12 relative overflow-hidden">
<div className="fixed inset-0 pointer-events-none">
<div className="absolute -top-1/2 -left-1/2 w-[200%] h-[200%] bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-indigo-900/20 via-slate-950 to-slate-950 animate-pulse"></div>
</div>
<div className="relative max-w-2xl mx-auto px-6 py-10">
<header className="text-center mb-12">
<div className="flex items-center justify-center gap-4 text-indigo-400 mb-2">
<i data-lucide="moon" className="w-5 h-5"></i>
<span className="text-xs tracking-[0.3em] uppercase">Mystic Runes</span>
<i data-lucide="sun" className="w-5 h-5"></i>
</div>
<h1 className="text-4xl md:text-5xl font-serif font-bold text-transparent bg-clip-text bg-gradient-to-r from-amber-200 to-indigo-200 mb-4">오늘의 룬 운세</h1>
</header>
<section className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-6 mb-10 shadow-lg">
<h2 className="text-lg font-bold text-indigo-300 mb-1">사용자 프로필</h2>
<div className="text-2xl font-serif text-white mb-2">{userBirthDate.year}년 {userBirthDate.month}월 {userBirthDate.day}일생</div>
<div className="bg-slate-900/50 rounded-lg p-4 border border-indigo-500/20 flex items-center gap-4">
<div className="text-4xl text-amber-400 font-bold w-12 text-center">{birthRune.symbol}</div>
<div>
<div className="font-bold text-amber-100">{birthRune.name} (페르쏘)</div>
<div className="text-xs text-slate-400">수호 룬: 비밀과 운명을 상징합니다.</div>
</div>
</div>
</section>
<section className="flex flex-col items-center min-h-[500px]">
<div className="mb-8 text-center">
{!hasDrawn ? <div className="animate-bounce-slow text-slate-300 text-lg">마음을 집중하고 카드를 눌러주세요.</div> : <div className="text-amber-200 text-lg font-serif">오늘 당신에게 전하는 메시지</div>}
</div>
<div className="relative mb-12 perspective">
{!hasDrawn ? (
<div onClick={drawRune} className={`w-64 h-96 bg-slate-800 rounded-xl shadow-2xl border-2 border-indigo-300/30 flex items-center justify-center cursor-pointer hover:scale-105 transition-all ${isRevealing ? 'animate-pulse' : ''}`}>
<i data-lucide="sparkles" className={`text-indigo-400 w-12 h-12 ${isRevealing ? 'animate-spin' : ''}`}></i>
</div>
) : (
<RuneCard {...dailyRune} isRevealed={true} isLarge={true} />
)}
</div>
<div className="flex gap-4">
{!hasDrawn ? (
<button onClick={drawRune} className="px-8 py-3 bg-gradient-to-r from-indigo-600 to-violet-600 text-white rounded-full font-bold shadow-lg active:scale-95">룬 뽑기</button>
) : (
<button onClick={() => setHasDrawn(false)} className="px-8 py-3 bg-slate-800 border border-slate-600 text-slate-200 rounded-full font-medium flex items-center gap-2 active:scale-95">
<i data-lucide="refresh-cw" className="w-4 h-4"></i> 다시 뽑기
</button>
)}
</div>
</section>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>