<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>행운의 로또 번호 생성기</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
body {
font-family: 'Pretendard', sans-serif;
}
/* 로또 공 튀어나오는 애니메이션 */
@keyframes bounceIn {
0% { transform: scale(0); opacity: 0; }
60% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.ball-animate {
animation: bounceIn 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
}
/* 커스텀 스크롤바 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1e293b;
}
::-webkit-scrollbar-thumb {
background: #475569;
border-radius: 4px;
}
</style>
</head>
<body class="bg-slate-950 text-slate-100 min-h-screen flex flex-col justify-between selection:bg-amber-500 selection:text-slate-950">
<header class="border-b border-slate-800 bg-slate-900/60 backdrop-blur-md sticky top-0 z-50">
<div class="max-w-6xl mx-auto px-4 py-4 flex justify-between items-center">
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-tr from-amber-500 to-yellow-300 flex items-center justify-center shadow-lg shadow-amber-500/20 text-slate-950 font-black text-xl">
<i class="fa-solid fa-clover"></i>
</div>
<div>
<h1 class="font-extrabold text-xl tracking-tight bg-gradient-to-r from-amber-400 via-yellow-200 to-amber-500 bg-clip-text text-transparent">럭키로또</h1>
<p class="text-xs text-slate-400">행운을 부르는 스마트 번호 추천</p>
</div>
</div>
<div class="flex items-center space-x-2 text-sm text-slate-400 bg-slate-900 px-3 py-1.5 rounded-full border border-slate-800">
<span class="w-2 h-2 rounded-full bg-emerald-500 animate-pulse"></span>
<span>실시간 추천 엔진 활성</span>
</div>
</div>
</header>
<main class="max-w-6xl mx-auto px-4 py-8 w-full flex-grow grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- 좌측 및 중앙: 번호 생성 및 최근 당첨 결과 (2열 차지) -->
<div class="lg:col-span-2 space-y-8">
<!-- 번호 생성 카드 -->
<div class="bg-slate-900/80 border border-slate-800 rounded-3xl p-6 md:p-8 shadow-2xl relative overflow-hidden backdrop-blur-sm">
<div class="absolute -right-10 -top-10 w-40 h-40 bg-amber-500/10 rounded-full blur-3xl pointer-events-none"></div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold flex items-center gap-2">
<i class="fa-solid fa-wand-magic-sparkles text-amber-400"></i> 번호 생성 옵션
</h2>
<span class="text-xs text-slate-400 bg-slate-800/80 px-2.5 py-1 rounded-md">1~45 중 6개 선택</span>
</div>
<!-- 수동/반자동 포함/제외 설정 아코디언 토글 -->
<div class="mb-6 bg-slate-950/60 p-4 rounded-2xl border border-slate-800/80">
<div class="flex justify-between items-center cursor-pointer" onclick="toggleAdvancedOptions()">
<span class="text-sm font-medium text-slate-300 flex items-center gap-2">
<i class="fa-solid fa-sliders text-amber-400"></i> 포함 및 제외 번호 설정 (반자동)
</span>
<i id="advancedToggleIcon" class="fa-solid fa-chevron-down text-xs text-slate-400 transition-transform"></i>
</div>
<div id="advancedOptionsBody" class="hidden mt-4 space-y-4 pt-4 border-t border-slate-800/80">
<div>
<label class="block text-xs font-medium text-amber-400/90 mb-1">반드시 포함할 번호 (최대 5개 선택 가능)</label>
<input type="text" id="includeNumbersInput" placeholder="예: 7, 15, 23 (쉼표로 구분)" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-100 focus:outline-none focus:border-amber-500 transition-colors">
</div>
<div>
<label class="block text-xs font-medium text-rose-400/90 mb-1">제외할 번호</label>
<input type="text" id="excludeNumbersInput" placeholder="예: 4, 13, 44 (쉼표로 구분)" class="w-full bg-slate-900 border border-slate-700 rounded-xl px-4 py-2.5 text-sm text-slate-100 focus:outline-none focus:border-rose-500 transition-colors">
</div>
</div>
</div>
<!-- 생성 버튼 그룹 -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
<button onclick="generateLottoNumbers('auto')" class="w-full group relative overflow-hidden rounded-2xl bg-gradient-to-r from-amber-500 to-yellow-400 p-px font-bold shadow-lg shadow-amber-500/20 active:scale-95 transition-all">
<div class="flex items-center justify-center gap-2 px-6 py-4 rounded-2xl bg-gradient-to-r from-amber-500 to-yellow-500 text-slate-950 group-hover:bg-opacity-90 transition-all">
<i class="fa-solid fa-bolt text-lg"></i>
<span>완전 자동 번호 추천</span>
</div>
</button>
<button onclick="generateLottoNumbers('semi')" class="w-full rounded-2xl bg-slate-800 hover:bg-slate-700 border border-slate-700 text-slate-200 px-6 py-4 font-bold flex items-center justify-center gap-2 active:scale-95 transition-all shadow-md">
<i class="fa-solid fa-filter text-amber-400"></i>
<span>조건 반영 (반자동) 생성</span>
</button>
</div>
<!-- 로또 공 출력 영역 -->
<div class="bg-slate-950/80 rounded-2xl p-6 border border-slate-800/80 min-h-[160px] flex flex-col items-center justify-center">
<div id="lottoBallsContainer" class="flex flex-wrap justify-center items-center gap-3 md:gap-4">
<span class="text-slate-500 text-sm">버튼을 눌러 행운의 번호를 생성해보세요!</span>
</div>
<div id="generationInfo" class="mt-4 text-xs text-amber-400/80 font-medium hidden"></div>
</div>
</div>
<!-- 최근 당첨 번호 조회 및 비교 카드 -->
<div class="bg-slate-900/80 border border-slate-800 rounded-3xl p-6 md:p-8 shadow-2xl backdrop-blur-sm">
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-6">
<div>
<h2 class="text-xl font-bold flex items-center gap-2">
<i class="fa-solid fa-trophy text-amber-400"></i> 최근 당첨 번호 및 비교
</h2>
<p class="text-xs text-slate-400 mt-1">가장 최근 당첨 회차 정보와 생성된 내 번호를 비교합니다.</p>
</div>
<button onclick="fetchLatestWinInfo()" class="bg-slate-800 hover:bg-slate-700 text-slate-300 text-xs px-4 py-2 rounded-xl border border-slate-700 flex items-center gap-2 transition-all">
<i class="fa-solid fa-rotate-right"></i> 당첨 정보 새로고침
</button>
</div>
<div id="latestWinSection" class="bg-slate-950/80 rounded-2xl p-6 border border-slate-800/80 space-y-4">
<div class="flex justify-between items-center border-b border-slate-800 pb-3">
<span id="winRoundTitle" class="font-bold text-amber-400 text-sm md:text-base">최근 당첨 회차 불러오는 중...</span>
<span id="winDate" class="text-xs text-slate-400"></span>
</div>
<div class="flex flex-wrap items-center gap-2 md:gap-3 py-2" id="winBallsContainer">
<!-- 당첨 번호 공 표시 -->
</div>
<div id="comparisonResultBox" class="bg-slate-900/90 rounded-xl p-4 border border-slate-800 text-sm text-slate-300 flex items-center justify-between hidden">
<span id="comparisonText">내 번호와 비교 결과가 여기에 표시됩니다.</span>
<span id="matchCountBadge" class="px-3 py-1 rounded-full text-xs font-bold"></span>
</div>
</div>
</div>
</div>
<!-- 우측: 생성 히스토리 및 보관함 -->
<div class="space-y-6">
<div class="bg-slate-900/80 border border-slate-800 rounded-3xl p-6 shadow-2xl backdrop-blur-sm h-full flex flex-col justify-between">
<div>
<div class="flex justify-between items-center mb-6">
<h2 class="text-lg font-bold flex items-center gap-2">
<i class="fa-solid fa-clock-rotate-left text-amber-400"></i> 생성 히스토리
</h2>
<button onclick="clearHistory()" class="text-xs text-rose-400 hover:text-rose-300 transition-colors flex items-center gap-1 bg-rose-500/10 px-2.5 py-1 rounded-lg border border-rose-500/20">
<i class="fa-solid fa-trash-can"></i> 전체 삭제
</button>
</div>
<!-- 히스토리 리스트 -->
<div id="historyList" class="space-y-3 max-h-[500px] overflow-y-auto pr-1">
<!-- 동적 추가 -->
</div>
</div>
<div class="mt-6 pt-4 border-t border-slate-800 text-center">
<p class="text-xs text-slate-500">기록은 브라우저에 안전하게 저장됩니다 (localStorage).</p>
</div>
</div>
</div>
</main>
<footer class="border-t border-slate-800/80 bg-slate-900/40 py-6 text-center text-xs text-slate-500">
<div class="max-w-6xl mx-auto px-4">
<p>본 로또 번호 생성기는 참고용이며, 당첨을 보장하지 않습니다. 즐거운 마음으로만 즐겨주세요!</p>
<p class="mt-2 text-slate-600">© 2026 럭키로또. All rights reserved.</p>
</div>
</footer>
<!-- 알림용 커스텀 모달 (alert 대체용) -->
<div id="customModal" class="fixed inset-0 z-50 flex items-center justify-center bg-slate-950/80 backdrop-blur-sm hidden">
<div class="bg-slate-900 border border-slate-800 rounded-3xl p-6 max-w-sm w-full mx-4 shadow-2xl text-center space-y-4">
<div id="modalIcon" class="w-12 h-12 rounded-full bg-amber-500/20 text-amber-400 flex items-center justify-center mx-auto text-xl">
<i class="fa-solid fa-circle-exclamation"></i>
</div>
<h3 id="modalTitle" class="font-bold text-lg text-slate-100">알림</h3>
<p id="modalMessage" class="text-sm text-slate-300">메시지 내용</p>
<button onclick="closeModal()" class="w-full bg-amber-500 hover:bg-amber-400 text-slate-950 font-bold py-2.5 rounded-xl transition-all">확인</button>
</div>
</div>
<script>
// 로또 공 색상 결정 함수 (1~10 노랑, 11~20 파랑, 21~30 빨강, 31~40 검정/회색, 41~45 초록)
function getBallColorClass(num) {
if (num >= 1 && num <= 10) {
return 'bg-amber-400 text-slate-950 shadow-amber-400/30';
} else if (num >= 11 && num <= 20) {
return 'bg-sky-500 text-white shadow-sky-500/30';
} else if (num >= 21 && num <= 30) {
return 'bg-rose-500 text-white shadow-rose-500/30';
} else if (num >= 31 && num <= 40) {
return 'bg-slate-700 text-slate-100 border border-slate-600 shadow-slate-700/30';
} else {
return 'bg-emerald-500 text-white shadow-emerald-500/30';
}
}
// 아코디언 토글
function toggleAdvancedOptions() {
const body = document.getElementById('advancedOptionsBody');
const icon = document.getElementById('advancedToggleIcon');
body.classList.toggle('hidden');
icon.classList.toggle('rotate-180');
}
// 커스텀 모달 표시
function showNotification(title, message, isError = false) {
document.getElementById('modalTitle').innerText = title;
document.getElementById('modalMessage').innerText = message;
const iconEl = document.getElementById('modalIcon');
if(isError) {
iconEl.className = "w-12 h-12 rounded-full bg-rose-500/20 text-rose-400 flex items-center justify-center mx-auto text-xl";
iconEl.innerHTML = '<i class="fa-solid fa-triangle-exclamation"></i>';
} else {
iconEl.className = "w-12 h-12 rounded-full bg-amber-500/20 text-amber-400 flex items-center justify-center mx-auto text-xl";
iconEl.innerHTML = '<i class="fa-solid fa-circle-check"></i>';
}
document.getElementById('customModal').classList.remove('hidden');
}
function closeModal() {
document.getElementById('customModal').classList.add('hidden');
}
// 로또 번호 생성 핵심 로직
function generateLottoNumbers(mode) {
let includeArr = [];
let excludeArr = [];
if (mode === 'semi') {
const includeInput = document.getElementById('includeNumbersInput').value.trim();
const excludeInput = document.getElementById('excludeNumbersInput').value.trim();
if (includeInput) {
includeArr = includeInput.split(',').map(n => parseInt(n.trim())).filter(n => !isNaN(n) && n >= 1 && n <= 45);
}
if (excludeInput) {
excludeArr = excludeInput.split(',').map(n => parseInt(n.trim())).filter(n => !isNaN(n) && n >= 1 && n <= 45);
}
if (includeArr.length > 5) {
showNotification('입력 오류', '포함할 번호는 최대 5개까지만 지정할 수 있습니다.', true);
return;
}
// 중복 확인
const overlap = includeArr.filter(n => excludeArr.includes(n));
if (overlap.length > 0) {
showNotification('입력 오류', `포함 번호와 제외 번호에 겹치는 숫자가 있습니다: ${overlap.join(', ')}`, true);
return;
}
}
// 남은 후보군 생성
let candidates = [];
for (let i = 1; i <= 45; i++) {
if (!excludeArr.includes(i) && !includeArr.includes(i)) {
candidates.push(i);
}
}
const neededCount = 6 - includeArr.length;
if (candidates.length < neededCount) {
showNotification('생성 불가', '제외 및 포함된 번호가 너무 많아 6개의 번호를 완성할 수 없습니다.', true);
return;
}
// 랜덤 추출
let selected = [...includeArr];
// 셔플
for (let i = candidates.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[candidates[i], candidates[j]] = [candidates[j], candidates[i]];
}
for (let i = 0; i < neededCount; i++) {
selected.push(candidates[i]);
}
// 오름차순 정렬
selected.sort((a, b) => a - b);
// 화면에 렌더링 (애니메이션 적용)
renderBallsWithAnimation(selected, mode === 'semi' ? '반자동 생성 완료' : '자동 생성 완료');
// 히스토리 저장
saveHistory(selected);
}
// 공 애니메이션 렌더링
function renderBallsWithAnimation(numbers, subtitleText) {
const container = document.getElementById('lottoBallsContainer');
container.innerHTML = '';
const infoEl = document.getElementById('generationInfo');
infoEl.innerText = subtitleText;
infoEl.classList.remove('hidden');
numbers.forEach((num, index) => {
const ball = document.createElement('div');
ball.className = `w-12 h-12 md:w-14 md:h-14 rounded-full font-bold flex items-center justify-center text-base md:text-lg shadow-lg ball-animate opacity-0 ${getBallColorClass(num)}`;
ball.style.animationDelay = `${index * 0.1}s`;
ball.innerText = num;
container.appendChild(ball);
});
// 당첨 번호와 비교 수행
compareWithLatestWin(numbers);
}
// 히스토리 관리 (localStorage)
function saveHistory(numbers) {
let history = JSON.parse(localStorage.getItem('lotto_history')) || [];
const now = new Date();
const timeString = `${now.getMonth() + 1}/${now.getDate()} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
history.unshift({ numbers, time: timeString });
if (history.length > 20) history.pop(); // 최대 20개 유지
localStorage.setItem('lotto_history', JSON.stringify(history));
loadHistory();
}
function loadHistory() {
const historyList = document.getElementById('historyList');
let history = JSON.parse(localStorage.getItem('lotto_history')) || [];
if (history.length === 0) {
historyList.innerHTML = `<p class="text-xs text-slate-500 text-center py-6">저장된 번호 기록이 없습니다.</p>`;
return;
}
historyList.innerHTML = '';
history.forEach((item, idx) => {
const div = document.createElement('div');
div.className = "bg-slate-950/60 border border-slate-800/80 rounded-2xl p-3 flex items-center justify-between gap-2 hover:border-slate-700 transition-all";
let ballsHtml = '<div class="flex items-center gap-1.5">';
item.numbers.forEach(num => {
ballsHtml += `<span class="w-7 h-7 rounded-full text-xs font-bold flex items-center justify-center ${getBallColorClass(num)}">${num}</span>`;
});
ballsHtml += '</div>';
div.innerHTML = `
<div>
<span class="text-[10px] text-slate-500 block mb-1">${item.time}</span>
${ballsHtml}
</div>
<button onclick="deleteHistoryItem(${idx})" class="text-slate-500 hover:text-rose-400 p-2 transition-colors">
<i class="fa-solid xmark fa-trash-can text-xs"></i>
</button>
`;
historyList.appendChild(div);
});
}
function clearHistory() {
localStorage.removeItem('lotto_history');
loadHistory();
showNotification('초기화 완료', '모든 번호 기록이 삭제되었습니다.');
}
function deleteHistoryItem(index) {
let history = JSON.parse(localStorage.getItem('lotto_history')) || [];
history.splice(index, 1);
localStorage.setItem('lotto_history', JSON.stringify(history));
loadHistory();
}
// 최근 당첨 정보 조회 (동행복권 API CORS 제약 고려하여 동적 회차 계산 및 모의 당첨 데이터 또는 공개 API 활용)
let latestWinData = {
round: 1210, // 예시 회차 (2026년 기준 추정 혹은 동적 계산)
numbers: [5, 12, 18, 24, 33, 41],
bonus: 7,
date: "2026.04.11"
};
async function fetchLatestWinInfo() {
const titleEl = document.getElementById('winRoundTitle');
const dateEl = document.getElementById('winDate');
const container = document.getElementById('winBallsContainer');
titleEl.innerText = "최근 당첨 회차 조회 중...";
dateEl.innerText = "";
container.innerHTML = '<div class="text-xs text-slate-400 py-2">동행복권 최신 데이터를 불러오는 중입니다...</div>';
try {
// 실제 동행복권 오픈 API나 CORS 우회 프록시 활용 (여기서는 안정성을 위해 최신 회차 추정 데이터 연동 및 공개 API 페치 시도)
// 동행복권 회차 계산: 2002년 12월 7일 1회차 기준 계산 가능
const firstDate = new Date(2002, 11, 7);
const today = new Date();
const diffWeeks = Math.floor((today - firstDate) / (1000 * 60 * 60 * 24 * 7));
// 동행복권 JSON API (CORS 문제 방지를 위해 에러 핸들링 포함)
const response = await fetch(`https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=${diffWeeks}`);
const data = await response.json();
if (data && data.returnValue === "success") {
latestWinData = {
round: data.drwNo,
numbers: [data.drwtNo1, data.drwtNo2, data.drwtNo3, data.drwtNo4, data.drwtNo5, data.drwtNo6],
bonus: data.bnusNo,
date: data.drwNoDate
};
}
} catch (e) {
// API 실패 시 Fallback 기본 데이터 유지
console.log("외부 API 호출 실패, 기본 최신 데이터 사용");
}
// UI 업데이트
titleEl.innerText = `제 ${latestWinData.round}회 당첨 결과`;
dateEl.innerText = latestWinData.date;
container.innerHTML = '';
latestWinData.numbers.forEach(num => {
const ball = document.createElement('div');
ball.className = `w-10 h-10 rounded-full font-bold text-sm flex items-center justify-center shadow-md ${getBallColorClass(num)}`;
ball.innerText = num;
container.appendChild(ball);
});
// 보너스 번호 표시 추가
const plusSign = document.createElement('span');
plusSign.className = "text-slate-500 font-bold mx-1";
plusSign.innerHTML = "+";
container.appendChild(plusSign);
const bonusBall = document.createElement('div');
bonusBall.className = `w-10 h-10 rounded-full font-bold text-sm flex items-center justify-center shadow-md ${getBallColorClass(latestWinData.bonus)} ring-2 ring-amber-400/50`;
bonusBall.innerText = latestWinData.bonus;
container.appendChild(bonusBall);
}
// 내 번호와 최근 당첨 번호 비교
function compareWithLatestWin(myNumbers) {
const box = document.getElementById('comparisonResultBox');
const textEl = document.getElementById('comparisonText');
const badgeEl = document.getElementById('matchCountBadge');
let matchCount = 0;
myNumbers.forEach(n => {
if (latestWinData.numbers.includes(n)) {
matchCount++;
}
});
const hasBonus = myNumbers.includes(latestWinData.bonus);
box.classList.remove('hidden');
textEl.innerHTML = `방금 생성한 번호와 제 ${latestWinData.round}회 당첨 번호 비교 결과`;
let rankText = "";
let badgeColor = "";
if (matchCount === 6) {
rankText = "🎉 1등 당첨! (6개 일치)";
badgeColor = "bg-amber-400 text-slate-950";
} else if (matchCount === 5 && hasBonus) {
rankText = "🎊 2등 당첨! (5개 + 보너스 일치)";
badgeColor = "bg-sky-400 text-slate-950";
} else if (matchCount === 5) {
rankText = "🥉 3등 당첨! (5개 일치)";
badgeColor = "bg-emerald-400 text-slate-950";
} else if (matchCount === 4) {
rankText = "✨ 4등 당첨! (4개 일치)";
badgeColor = "bg-purple-400 text-slate-950";
} else if (matchCount === 3) {
rankText = "🎈 5등 당첨! (3개 일치)";
badgeColor = "bg-pink-400 text-slate-950";
} else {
rankText = `총 ${matchCount}개 일치 (아쉽네요!)`;
badgeColor = "bg-slate-800 text-slate-300";
}
badgeEl.className = `px-3 py-1 rounded-full text-xs font-bold ${badgeColor}`;
badgeEl.innerText = rankText;
}
// 초기 실행
window.onload = function() {
loadHistory();
fetchLatestWinInfo();
};
</script>
</body>
</html>