Files
union-arena-deck-builder/cardList.html

894 lines
50 KiB
HTML

<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-4516420168710424" crossorigin="anonymous"></script>
<title>Card List</title>
<script src="/script/navigation.js"></script>
<link rel="stylesheet" href="./style/output.css" />
<script type="module" src="/i18n/i18n.js"></script>
<link href="/style/navigation.css" rel="stylesheet" />
<link href="/style/style.css" rel="stylesheet" />
<link href="/style/output.css" rel="stylesheet" />
<style>
body { background-color: #f8fafc;}
#card-grid { padding-bottom: 180px !important;}
.cardContainer { transition: all 0.2s; }
#cardList { padding-bottom: 120px; }
.fixed-footer { background: rgba(255, 255, 255, 0.9); backdrop-filter: blur(10px); border-top: 1px solid #e2e8f0; }
.overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
z-index: 1000;
justify-content: center;
align-items: center;
}
.overlay img {
max-width: 90%;
max-height: 90%;
object-fit: contain;
}
.cardContainer {
background: white;
border-radius: 12px;
padding: 12px;
border: 1px solid #e2e8f0;
transition: all 0.2s;
}
.cardContainer:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.fixed-footer {
background: rgba(30, 41, 59, 0.95);
backdrop-filter: blur(8px);
color: white;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.color-filter-btn {
transition: all 0.2s ease;
outline: 2px solid transparent;
outline-offset: -2px;
opacity: 0.5;
}
.color-filter-btn.is-active {
opacity: 1 !important;
outline: 2px solid currentColor !important;
background-color: white !important;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div id="app">
<div id="loading-screen" class="fixed inset-0 z-50 bg-white flex items-center justify-center" style="display: none">
<div class="animate-spin rounded-full h-12 w-12 border-4 border-blue-500 border-t-transparent"></div>
</div>
<div class="sticky top-0 z-50 h-16 bg-white/80 backdrop-blur-md border-b px-4 py-3 flex justify-between items-center">
<button onclick="history.back()" class="font-bold text-slate-600 hover:text-blue-600 cursor-pointer">← BACK</button>
<div class="font-bold text-slate-800">
<span id="i18n-searchResults">검색결과</span>
:
<span id="SearchResultsCounter" class="text-blue-600">0</span>
</div>
</div>
<div class="max-w-[1400px] mx-auto p-4 flex flex-col lg:flex-row gap-6">
<aside class="w-full lg:w-64 space-y-4 lg:sticky lg:top-20 lg:h-fit">
<div class="bg-white p-5 rounded-2xl border border-slate-200 shadow-sm">
<button id="resetSelection" class="w-full mb-4 flex items-center justify-center gap-2 py-2 bg-red-50 hover:bg-red-100 text-red-600 rounded-xl font-bold text-xs transition-all border border-red-100">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
<span id="i18n-resetBtn">선택 초기화 (RESET)</span>
</button>
<div class="mb-4">
<label class="block text-xs font-bold text-slate-400 uppercase mb-2">Search</label>
<input type="text" id="searchQuery" class="w-full border border-slate-200 rounded-lg p-2 focus:ring-2 focus:ring-blue-500 outline-none transition-all" placeholder="카드 번호 입력" />
</div>
<div class="mb-4">
<label class="block text-xs font-bold text-slate-400 uppercase mb-2">Color</label>
<div class="grid grid-cols-3 gap-2">
<button class="color-filter-btn border rounded-md py-1 text-xs font-bold transition-all" data-color="All">All</button>
<button class="color-filter-btn border border-red-100 bg-red-50 text-red-500 rounded-md py-1 text-xs font-bold transition-all" data-color="Red">Red</button>
<button class="color-filter-btn border border-blue-100 bg-blue-50 text-blue-500 rounded-md py-1 text-xs font-bold transition-all" data-color="Blue">Blue</button>
<button class="color-filter-btn border border-green-100 bg-green-50 text-green-500 rounded-md py-1 text-xs font-bold transition-all" data-color="Green">Green</button>
<button class="color-filter-btn border border-yellow-100 bg-yellow-50 text-yellow-600 rounded-md py-1 text-xs font-bold transition-all" data-color="Yellow">Yellow</button>
<button class="color-filter-btn border border-purple-100 bg-purple-50 text-purple-500 rounded-md py-1 text-xs font-bold transition-all" data-color="Purple">Purple</button>
</div>
</div>
<div class="mb-4">
<label class="block text-xs font-bold text-slate-400 uppercase mb-2">Series Filter</label>
<div id="seriesFilterContainer" class="flex flex-wrap gap-2">ㅅㄷㄴㅅ</div>
</div>
<div class="flex lg:flex-col">
<div class="w-full space-y-2 lg:pb-4">
<label class="block text-xs font-bold text-slate-400 uppercase mb-2">Type</label>
<label class="flex items-center gap-2 text-sm font-medium cursor-pointer">
<input type="checkbox" id="includeBasic" checked class="rounded border-slate-300" />
Basic
</label>
<label class="flex items-center gap-2 text-sm font-medium cursor-pointer">
<input type="checkbox" id="includeAP" checked class="rounded border-slate-300" />
AP
</label>
<label class="flex items-center gap-2 text-sm font-medium cursor-pointer">
<input type="checkbox" id="includeParallel" checked class="rounded border-slate-300" />
Parallel
</label>
</div>
<div class="w-full lg:pt-4 lg:border-t border-slate-100 space-y-2">
<label class="block text-xs font-bold text-slate-400 uppercase mb-2">Trigger</label>
<label class="flex items-center gap-2 text-xs font-medium cursor-pointer">
<input type="radio" name="triggerType" value="allTrigger" checked />
All
</label>
<label class="flex items-center gap-2 text-xs font-medium cursor-pointer">
<input type="radio" name="triggerType" value="specialTrigger" />
Special
</label>
<label class="flex items-center gap-2 text-xs font-medium cursor-pointer">
<input type="radio" name="triggerType" value="colorTrigger" />
Color
</label>
<label class="flex items-center gap-2 text-xs font-medium cursor-pointer">
<input type="radio" name="triggerType" value="finalTrigger" />
Final
</label>
</div>
</div>
</div>
</aside>
<main class="flex-1">
<div id="card-grid" class="grid grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3 sm:gap-4"></div>
</main>
</div>
<div class="fixed bottom-0 left-0 right-0 fixed-footer p-3 sm:p-4 z-50 shadow-[0_-10px_20px_rgba(0,0,0,0.05)]">
<div class="max-w-7xl mx-auto flex flex-col gap-3">
<div class="flex flex-wrap justify-center items-center gap-x-3 gap-y-1 text-[10px] sm:text-xs font-bold text-slate-600 select-none">
<div class="flex items-center gap-1">
<span class="text-slate-400">COLOR</span>
<span id="colorCardCount" class="text-blue-600">0</span>
/4
</div>
<span class="text-slate-200">|</span>
<div class="flex items-center gap-1">
<span class="text-slate-400">FINAL</span>
<span id="finalCardCount" class="text-blue-600">0</span>
/4
</div>
<span class="text-slate-200">|</span>
<div class="flex items-center gap-1">
<span class="text-slate-400">SPECIAL</span>
<span id="specialCardCount" class="text-blue-600">0</span>
/4
</div>
<span class="text-slate-200">|</span>
<div class="flex items-center gap-1">
<span class="text-slate-400">KEY</span>
<span id="keyCardCount" class="text-indigo-600">0</span>
/4
</div>
<span class="text-slate-200">|</span>
<div class="flex items-center gap-1 bg-slate-100 px-2 py-0.5 rounded-full">
<span class="text-slate-500">TOTAL</span>
<span id="totalCardCount" class="text-blue-600">0</span>
</div>
</div>
<button id="goToResultPage" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-xl font-bold text-sm transition-all shadow-lg active:scale-95">MAKE DECK LIST</button>
</div>
</div>
</div>
<div id="imageOverlay" class="overlay">
<img src="" alt="Card Preview" />
</div>
<div id="resetModal" class="overlay" style="display: none; background: rgba(15, 23, 42, 0.7); backdrop-filter: blur(4px)">
<div class="bg-white rounded-2xl p-6 max-w-sm w-[90%] shadow-2xl border border-slate-200" onclick="event.stopPropagation()">
<div class="flex flex-col items-center text-center">
<div class="w-12 h-12 bg-red-50 rounded-full flex items-center justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-red-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</div>
<h3 id="i18n-modalTitle" class="text-lg font-bold text-slate-900 mb-2">선택 초기화</h3>
<p id="i18n-modalDesc" class="text-sm text-slate-500 mb-6">
현재까지 선택한 모든 카드가 사라집니다.
<br />
정말 초기화하시겠습니까?
</p>
<div class="flex w-full gap-3">
<button id="cancelReset" class="flex-1 py-3 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold text-sm transition-all cursor-pointer">취소</button>
<button
id="confirmReset"
style="background-color: #ef4444 !important; color: white !important; cursor: pointer"
onmouseover="this.style.backgroundColor = '#dc2626'"
onmouseout="this.style.backgroundColor = '#ef4444'"
class="flex-1 py-3 rounded-xl text-white font-bold text-sm transition-all shadow-md">
초기화 실행
</button>
</div>
</div>
</div>
</div>
<script>
let cardList = [];
let selectedCards = [];
let selectedColors = new Set(['All']);
const getEl = () => ({
searchQuery: document.getElementById('searchQuery'),
cardGrid: document.getElementById('card-grid'),
loadingScreen: document.getElementById('loading-screen'),
resultsCounter: document.getElementById('SearchResultsCounter'),
keyCount: document.getElementById('keyCardCount'),
totalCount: document.getElementById('totalCardCount'),
finalCount: document.getElementById('finalCardCount'),
colorCount: document.getElementById('colorCardCount'),
specialCount: document.getElementById('specialCardCount'),
});
const seriesKey = new URLSearchParams(window.location.search).get('series');
const nameKey = new URLSearchParams(window.location.search).get('seriesName');
let t = {};
async function init() {
const urlParams = new URLSearchParams(window.location.search);
const lang = urlParams.get('lang') || localStorage.getItem('lang') || 'ko';
if (!seriesKey) return;
const el = getEl();
if (el.loadingScreen) el.loadingScreen.style.display = 'flex';
try {
// 1. 다국어 데이터 로드
const tResponse = await fetch(`./i18n/translations.${lang}.json`);
t = await tResponse.json();
applyI18n();
const module = await import(`./datas/${seriesKey}.js`);
cardList = module.cardList;
initSeriesFilter();
const cardsParam = new URLSearchParams(window.location.search).get('cards');
const savedData = cardsParam ? decodeURIComponent(cardsParam) : localStorage.getItem('selectedCards');
if (savedData) {
const parsed = JSON.parse(decodeURIComponent(savedData));
cardList = syncCardData(cardList, parsed);
selectedCards = parsed;
}
applyInitialFilter();
updateCounters();
syncToStorageAndCounters();
} catch (err) {
console.error('Data load error:', err);
} finally {
if (el.loadingScreen) el.loadingScreen.style.display = 'none';
}
}
// 1. 데이터 로드 후 시리즈 목록 추출
const allSeries = [...new Set(cardList.map((card) => card.Number.split('/')[0]))];
// 2. 선택된 시리즈를 관리할 Set (기본 "All")
let selectedSeries = new Set(['All']);
// 3. 필터 버튼 렌더링 함수
function renderSeriesFilters() {
const container = document.getElementById('seriesFilterContainer');
// "All" 버튼 추가
let html = `
<button data-series="All" class="series-btn px-3 py-1 rounded-full text-xs font-bold border transition-all ${selectedSeries.has('All') ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200 text-slate-500 hover:border-blue-400'}">
All
</button>
`;
// 데이터에서 추출한 시리즈 버튼들 추가
allSeries.forEach((series) => {
const isActive = selectedSeries.has(series);
html += `
<button data-series="${series}" class="series-btn px-3 py-1 rounded-full text-xs font-bold border transition-all ${isActive ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200 text-slate-500 hover:border-blue-400'}">
${series}
</button>
`;
});
container.innerHTML = html;
// 클릭 이벤트 바인딩
document.querySelectorAll('.series-btn').forEach((btn) => {
btn.addEventListener('click', (e) => {
const series = e.target.dataset.series;
toggleSeriesFilter(series);
});
});
}
// 4. 필터 선택 로직
function toggleSeriesFilter(series) {
if (series === 'All') {
selectedSeries.clear();
selectedSeries.add('All');
} else {
selectedSeries.delete('All');
if (selectedSeries.has(series)) {
selectedSeries.delete(series);
} else {
selectedSeries.add(series);
}
// 아무것도 선택 안되면 다시 "All"로
if (selectedSeries.size === 0) selectedSeries.add('All');
}
renderSeriesFilters(); // 버튼 UI 업데이트
updateCardList(); // 카드 목록 새로고침 (필터 적용)
}
function displayCardList(list) {
const el = getEl();
if (!el.cardGrid) return;
el.cardGrid.innerHTML = '';
if (el.resultsCounter) el.resultsCounter.textContent = list.length;
if (list.length === 0) {
el.cardGrid.innerHTML = `
<div class="col-span-full py-20 flex flex-col items-center justify-center text-slate-400">
<svg ...></svg>
<p class="text-sm font-bold">${t.noSearchResults || '검색 결과 없음'}</p>
<p class="text-xs mt-1">${t.noSearchResultsDesc || ''}</p>
</div>
`;
return;
}
list.forEach((card, index) => {
const cardContainer = document.createElement('div');
cardContainer.className = 'cardContainer group flex flex-col p-2 bg-white rounded-lg border border-slate-200 shadow-sm hover:border-blue-300 transition-colors';
cardContainer.setAttribute('data-imgsrc', card.imgSrc);
const fileName = card.imgSrc.split('/').pop();
const imgSrc = `images/${seriesKey}/${fileName}`;
cardContainer.innerHTML = `
<div class="card_image cursor-pointer relative w-full mb-2 rounded overflow-hidden bg-slate-50" style="aspect-ratio: 2/3;">
<img src="${imgSrc}" class="absolute inset-0 w-full h-full object-contain transition-transform"
onerror="console.error('이미지 로드 실패:', '${card.Number}', '${imgSrc}'); this.src='/images/uapr/comingsoon.png'; this.onerror=null;">
</div>
<div class="flex justify-between items-center mb-1.5 px-0.5">
<div class="font-bold px-1.5 py-0.5 rounded text-[8px] lg:text-[9px] leading-none truncate" style="${getRarityStyle(card.rarity)}">
${card.rarity || ''}
</div>
<div class="text-[8px] lg:text-[9px] text-slate-400 font-mono truncate">
${card.color === 'AP' ? 'AP' : card.Number.slice(-5)}
</div>
</div>
<div class="grid grid-cols-3 items-center bg-slate-100 rounded-md p-0.5 mb-1.5 h-7 lg:h-8 overflow-hidden text-center">
<button onclick="handleCount('${card.imgSrc}', -1, event)"
class="minusBtn h-full w-full flex items-center justify-center rounded bg-white shadow-sm text-xs font-bold text-slate-600 hover:bg-red-50 transition-all ${card.count === 0 ? 'invisible' : 'visible'}">
-
</button>
<span class="countText text-xs lg:text-sm font-bold text-slate-800 leading-none">
${card.count}
</span>
<button onclick="handleCount('${card.imgSrc}', 1, event)"
class="plusBtn h-full w-full flex items-center justify-center rounded bg-white shadow-sm text-xs font-bold text-slate-600 hover:bg-blue-50 transition-all ${isMax(card) ? 'invisible' : 'visible'}">
+
</button>
</div>
<button onclick="handleKey('${card.imgSrc}', event)"
class="keyBtn w-full py-1 rounded text-[9px] lg:text-[10px] font-bold border transition-all ${card.key && card.count > 0 ? 'bg-indigo-600 border-indigo-600 text-white shadow-md' : 'bg-white border-slate-200 text-slate-400'}">
KEY
</button>
`;
cardContainer.querySelector('.card_image').onclick = () => showOverlay(card, index, list);
el.cardGrid.appendChild(cardContainer);
});
}
function updateColorFilterUI() {
document.querySelectorAll('.color-filter-btn').forEach((btn) => {
const isSelected = selectedColors.has(btn.dataset.color);
if (isSelected) {
// 활성화: 테두리 두께를 바꾸는 대신 shadow(ring)를 사용하여 레이아웃 고정
btn.style.opacity = '1';
btn.style.boxShadow = 'inset 0 0 0 2px currentColor, 0 4px 6px -1px rgba(0,0,0,0.1)';
btn.style.fontWeight = '800'; // 글자 굵기로 강조
btn.style.backgroundColor = 'white'; // 배경을 밝게
} else {
// 비활성화
btn.style.opacity = '0.4';
btn.style.boxShadow = 'none';
btn.style.fontWeight = 'bold';
btn.style.backgroundColor = 'transparent';
}
});
}
function applyI18n() {
const safeSet = (id, key, isHTML = false) => {
const el = document.getElementById(id);
if (el && t[key]) {
isHTML ? (el.innerHTML = t[key]) : (el.textContent = t[key]);
}
};
safeSet('i18n-back', 'back');
safeSet('i18n-searchResults', 'searchResults');
safeSet('i18n-resetBtn', 'resetSelection');
safeSet('i18n-labelSearch', 'search');
safeSet('i18n-labelColor', 'color');
safeSet('i18n-labelSeries', 'seriesFilter');
safeSet('i18n-labelType', 'type');
safeSet('i18n-labelTrigger', 'trigger');
safeSet('i18n-makeDeck', 'makeDeckList');
safeSet('i18n-modalTitle', 'resetModalTitle');
safeSet('i18n-modalDesc', 'resetModalDesc', true);
safeSet('i18n-cancel', 'cancel');
safeSet('i18n-confirm', 'confirm');
// 플레이스홀더는 별도 처리
const searchInput = document.getElementById('searchQuery');
if (searchInput && t.searchPlaceholder) searchInput.placeholder = t.searchPlaceholder;
}
// 전역 핸들러
window.handleCount = function (imgSrc, delta, event) {
event.stopPropagation();
const card = cardList.find((c) => c.imgSrc === imgSrc);
if (!card) return;
// 1. 데이터 업데이트
const currentTotal = cardList.filter((c) => c.Number === card.Number).reduce((s, c) => s + c.count, 0);
if (delta > 0 && currentTotal < (card.maxCount || 4)) {
card.count++;
} else if (delta < 0 && card.count > 0) {
card.count--;
if (card.count === 0) card.key = false;
}
// 2. UI 업데이트
const container = document.querySelector(`.cardContainer[data-imgsrc="${imgSrc}"]`);
if (container) {
const minusBtn = container.querySelector('.minusBtn');
const plusBtn = container.querySelector('.plusBtn');
const countSpan = container.querySelector('.countText');
const keyBtn = container.querySelector('.keyBtn'); // 클래스로 직접 참조
if (countSpan) countSpan.textContent = card.count;
// 마이너스 버튼 제어
if (minusBtn) {
if (card.count > 0) {
minusBtn.classList.remove('invisible');
minusBtn.classList.add('visible');
} else {
minusBtn.classList.remove('visible');
minusBtn.classList.add('invisible');
}
}
// 플러스 버튼 제어 (isMax 결과에 따라 즉시 반영)
if (plusBtn) {
if (isMax(card)) {
plusBtn.classList.remove('visible');
plusBtn.classList.add('invisible');
} else {
plusBtn.classList.remove('invisible');
plusBtn.classList.add('visible');
}
}
// 키 버튼 상태 업데이트
if (keyBtn) {
keyBtn.className = `keyBtn w-full py-1 rounded text-[9px] lg:text-[10px] font-bold border transition-all ${card.key && card.count > 0 ? 'bg-indigo-600 border-indigo-600 text-white shadow-md' : 'bg-white border-slate-200 text-slate-400'}`;
}
}
syncToStorageAndCounters();
};
window.handleKey = function (imgSrc, event) {
event.stopPropagation();
const card = cardList.find((c) => c.imgSrc === imgSrc);
if (!card || card.count === 0) return;
const currentKeys = cardList.filter((c) => c.key && c.count > 0).length;
if (card.key) {
card.key = false;
} else if (currentKeys < 4) {
card.key = true;
}
const container = document.querySelector(`.cardContainer[data-imgsrc="${imgSrc}"]`);
if (container) {
// 특정 클래스(.keyBtn)를 가진 요소를 정확히 찾아 변경
const keyBtn = container.querySelector('.keyBtn');
if (keyBtn) {
keyBtn.className = `keyBtn w-full py-1 rounded text-[9px] lg:text-[10px] font-bold border transition-all ${card.key && card.count > 0 ? 'bg-indigo-600 border-indigo-600 text-white shadow-md' : 'bg-white border-slate-200 text-slate-400'}`;
}
}
syncToStorageAndCounters();
};
function isMax(card) {
return cardList.filter((c) => c.Number === card.Number).reduce((s, c) => s + c.count, 0) >= (card.maxCount || 4);
}
function updateCardList() {
const el = getEl();
const query = el.searchQuery.value.toLowerCase();
const triggerRadio = document.querySelector('input[name="triggerType"]:checked');
const trigger = triggerRadio ? triggerRadio.value : 'allTrigger';
const isBasicEnabled = document.getElementById('includeBasic').checked;
const isAPEnabled = document.getElementById('includeAP').checked;
const isParallelEnabled = document.getElementById('includeParallel').checked;
const filtered = cardList.filter((card) => {
// 1. 유형 필터 (Basic, AP, Parallel) - 조건 우선순위 재정립
let typeMatch = false;
// 패러렐 우선 체크: 패러렐이 켜져 있고 해당 카드가 패러렐인 경우
if (card.parallel) {
if (isParallelEnabled) typeMatch = true;
}
// 패러렐이 아닌 카드들 중에서 AP와 Basic 체크
else {
if (card.color === 'AP') {
if (isAPEnabled) typeMatch = true;
} else {
if (isBasicEnabled) typeMatch = true;
}
}
if (!typeMatch) return false;
// 2. 색상 필터
if (!selectedColors.has('All')) {
const cardColor = card.color === 'Multi' ? 'ALL COLOR' : card.color;
if (!selectedColors.has(cardColor)) return false;
}
// 3. 트리거 필터
if (trigger !== 'allTrigger') {
const tMap = { specialTrigger: 'SPECIAL', colorTrigger: 'COLOR', finalTrigger: 'FINAL' };
if (card.trigger !== tMap[trigger]) return false;
}
// 4. 시리즈 그룹 필터
const cardGroup = card.Number.split(/[/-]/)[0];
const matchesSeries = selectedSeriesGroups.has('All') || selectedSeriesGroups.has(cardGroup);
if (!matchesSeries) return false;
// 5. 검색어 필터
if (query && !card.Number.toLowerCase().includes(query)) return false;
return true;
});
displayCardList(filtered);
}
function syncCardData(base, saved) {
const map = new Map(base.map((c) => [c.imgSrc, c]));
saved.forEach((s) => {
const item = map.get(s.imgSrc);
if (item) {
item.count = s.count;
item.key = s.key;
}
});
return Array.from(map.values());
}
function saveAndRefresh() {
selectedCards = cardList.filter((c) => c.count > 0);
const encoded = encodeURIComponent(JSON.stringify(selectedCards));
localStorage.setItem('selectedCards', encoded);
const url = new URL(window.location.href);
url.searchParams.set('cards', encoded);
window.history.replaceState({}, '', url);
updateCounters();
updateCardList();
}
function updateCounters() {
const el = getEl(); // 이전 코드에서 정의한 DOM 참조 함수
let s = { k: 0, c: 0, f: 0, sp: 0, t: 0 };
cardList.forEach((c) => {
if (c.count > 0) {
if (c.key) s.k++;
if (c.trigger === 'COLOR') s.c += c.count;
if (c.trigger === 'FINAL') s.f += c.count;
if (c.trigger === 'SPECIAL') s.sp += c.count;
s.t += c.count;
}
});
// 각 요소를 찾아 텍스트 업데이트 (존재 여부 확인 포함)
const updateText = (id, value) => {
const element = document.getElementById(id);
if (element) element.textContent = value;
};
updateText('colorCardCount', s.c);
updateText('finalCardCount', s.f);
updateText('specialCardCount', s.sp);
updateText('keyCardCount', s.k);
updateText('totalCardCount', s.t);
}
function getRarityStyle(r) {
if (!r) return '';
r = r.trim();
if (r.includes('★') || r === 'UR') return 'background: linear-gradient(45deg, #f472b6, #60a5fa); color: white;';
if (['SR', 'PcSR'].includes(r)) return 'background: #F2F320; color: black;';
return 'background: #f1f5f9; color: #64748b;';
}
function showOverlay(card, index, list) {
const overlay = document.getElementById('imageOverlay');
const img = overlay.querySelector('img');
img.src = `images/${seriesKey}/${card.imgSrc.split('/').pop()}`;
overlay.style.display = 'flex';
overlay.onclick = (e) => {
if (e.target === overlay) overlay.style.display = 'none';
};
}
function applyInitialFilter() {
document.getElementById('includeBasic').checked = true;
document.getElementById('includeAP').checked = true;
document.getElementById('includeParallel').checked = true;
updateCardList();
}
const resetModal = document.getElementById('resetModal');
// 초기화 실행 함수
function executeReset() {
// 1. 데이터 초기화
cardList.forEach((card) => {
card.count = 0;
card.key = false;
});
selectedCards = [];
// 2. 필터 상태값 초기화
selectedColors = new Set(['All']);
selectedSeriesGroups = new Set(['All']);
const searchQueryEl = document.getElementById('searchQuery');
if (searchQueryEl) searchQueryEl.value = '';
// 유형(Type) 체크박스 초기화 (모두 체크 상태로)
['includeBasic', 'includeAP', 'includeParallel'].forEach(id => {
const el = document.getElementById(id);
if (el) el.checked = true;
});
// 트리거 라디오 버튼 초기화 (All 선택)
const allTriggerRadio = document.querySelector('input[name="triggerType"][value="allTrigger"]');
if (allTriggerRadio) allTriggerRadio.checked = true;
// 3. UI 컴포넌트 갱신
updateColorFilterUI(); // 색상 버튼 스타일 초기화
renderSeriesFilters(); // 시리즈 버튼 스타일 초기화 (또는 initSeriesFilter)
// 4. 저장소 및 URL 동기화
localStorage.removeItem('selectedCards');
const url = new URL(window.location.href);
url.searchParams.delete('cards');
window.history.replaceState({}, '', url);
// 5. 전체 갱신
updateCounters();
updateCardList();
syncToStorageAndCounters(); // 버튼 활성화 상태까지 체크
// 6. 모달 닫기
const resetModal = document.getElementById('resetModal');
if (resetModal) resetModal.style.display = 'none';
}
document.addEventListener('DOMContentLoaded', () => {
init();
document.querySelectorAll('.color-filter-btn').forEach((btn) => {
btn.addEventListener('click', () => {
const color = btn.dataset.color;
if (color === 'All') {
selectedColors.clear();
selectedColors.add('All');
} else {
selectedColors.delete('All');
selectedColors.has(color) ? selectedColors.delete(color) : selectedColors.add(color);
if (selectedColors.size === 0) selectedColors.add('All');
}
updateColorFilterUI();
updateCardList();
});
});
['searchQuery', 'includeBasic', 'includeAP', 'includeParallel'].forEach((id) => {
document.getElementById(id).addEventListener('input', updateCardList);
});
document.querySelectorAll('input[name="triggerType"]').forEach((r) => {
r.addEventListener('change', updateCardList);
});
document.getElementById('goToResultPage').addEventListener('click', () => {
if (this.disabled) return; // 비활성화 상태면 무시
// 현재 페이지에서 선택된(cardList에 있는) 카드만 필터링해서 전송
const currentSeriesSelected = cardList.filter(c => c.count > 0);
if (currentSeriesSelected.length === 0) {
alert(translations.noCardsSelected || "선택된 카드가 없습니다.");
return;
}
const queryParams = encodeURIComponent(JSON.stringify(selectedCards));
window.location.href = `/result.html?series=${seriesKey}&seriesName=${nameKey}&cards=${queryParams}`;
});
const resetModal = document.getElementById('resetModal');
const resetBtn = document.getElementById('resetSelection');
const cancelBtn = document.getElementById('cancelReset');
const confirmBtn = document.getElementById('confirmReset');
// 1. 초기화 버튼 클릭 시 모달 표시
if (resetBtn) {
resetBtn.onclick = (e) => {
e.preventDefault();
resetModal.style.setProperty('display', 'flex', 'important');
};
}
// 2. 취소 버튼 클릭 시 모달 닫기
if (cancelBtn) {
cancelBtn.onclick = () => {
resetModal.style.display = 'none';
};
}
// 3. 실행 버튼 클릭 시 데이터 초기화 및 닫기
if (confirmBtn) {
confirmBtn.onclick = () => {
executeReset(); // 이전에 만든 데이터 초기화 함수 호출
resetModal.style.display = 'none';
};
}
// 4. 배경 클릭 시 닫기
resetModal.onclick = (e) => {
if (e.target === resetModal) {
resetModal.style.display = 'none';
}
};
});
// 공통 저장 및 카운터 업데이트 함수
function syncToStorageAndCounters() {
// 1. 전체 선택된 카드 추출
selectedCards = cardList.filter((c) => c.count > 0);
// 2. 현재 시리즈에 해당하는 카드가 있는지 확인 (활성화 조건)
const hasCardsInCurrentSeries = selectedCards.length > 0;
const resultBtn = document.getElementById('goToResultPage');
if (resultBtn) {
if (hasCardsInCurrentSeries) {
resultBtn.disabled = false;
resultBtn.classList.remove('opacity-50', 'cursor-not-allowed');
resultBtn.classList.add('bg-blue-600', 'hover:bg-blue-700');
} else {
resultBtn.disabled = true;
resultBtn.classList.add('opacity-50', 'cursor-not-allowed');
resultBtn.classList.remove('bg-blue-600', 'hover:bg-blue-700');
}
}
// 3. 로컬 스토리지 저장 (현재 시리즈 데이터만 깨끗하게 유지)
const encoded = encodeURIComponent(JSON.stringify(selectedCards));
localStorage.setItem('selectedCards', encoded);
// 4. URL 업데이트 (새로고침 시 데이터 유지용)
const url = new URL(window.location.href);
url.searchParams.set('cards', encoded);
window.history.replaceState({}, '', url);
updateCounters(); // 하단 바 숫자만 업데이트
}
let selectedSeriesGroups = new Set(['All']);
function initSeriesFilter() {
const container = document.getElementById('seriesFilterContainer');
// 데이터에서 시리즈 목록 추출
const seriesGroups = [...new Set(cardList.map((c) => c.Number.split(/[/-]/)[0]))]; //
// 초기 HTML 생성 (All 버튼 포함)
container.innerHTML = `
<button data-series="All" class="series-btn px-3 py-1 rounded-full text-xs font-bold border transition-all ${selectedSeriesGroups.has('All') ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200 text-slate-500'}">
All
</button>
`;
seriesGroups.forEach((group) => {
const isSel = selectedSeriesGroups.has(group);
container.innerHTML += `
<button data-series="${group}" class="series-btn px-3 py-1 rounded-full text-xs font-bold border transition-all ${isSel ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200 text-slate-500 hover:border-blue-400'}">
${group}
</button>
`;
});
// 클릭 이벤트 핸들러
container.addEventListener('click', (e) => {
const btn = e.target.closest('.series-btn'); // 정확한 버튼 클릭 감지
if (!btn) return;
const series = btn.dataset.series;
if (series === 'All') {
selectedSeriesGroups.clear();
selectedSeriesGroups.add('All');
} else {
selectedSeriesGroups.delete('All');
if (selectedSeriesGroups.has(series)) {
selectedSeriesGroups.delete(series);
} else {
selectedSeriesGroups.add(series);
}
// 아무것도 선택 안된 경우 다시 All 선택
if (selectedSeriesGroups.size === 0) selectedSeriesGroups.add('All');
}
// 1. 모든 버튼 UI 일괄 업데이트
document.querySelectorAll('.series-btn').forEach((b) => {
const isSel = selectedSeriesGroups.has(b.dataset.series);
b.className = `series-btn px-3 py-1 rounded-full text-xs font-bold border transition-all ${isSel ? 'bg-blue-600 border-blue-600 text-white' : 'bg-white border-slate-200 text-slate-500 hover:border-blue-400'}`;
});
// 2. 필터링된 목록 다시 그리기
updateCardList();
});
}
</script>
</body>
</html>