894 lines
50 KiB
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>
|