v4.0.0: 덱 UX·i18n·썸네일 저장 개선 및 413 수정

즐겨찾기·홀드 미리보기·페이지 도움말·3개국어 번역을 추가하고, 덱 저장 시 썸네일을 1200px로 통일하며 Nginx/API 업로드 제한을 상향했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 18:50:08 +09:00
parent dbf02241e2
commit 2ecf6201c0
29 changed files with 2195 additions and 326 deletions

View File

@@ -83,7 +83,7 @@
</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 type="button" onclick="goToHome()" class="font-bold text-slate-600 hover:text-blue-600 cursor-pointer">← 홈</button>
<button type="button" id="i18n-home" onclick="goToHome()" class="font-bold text-slate-600 hover:text-blue-600 cursor-pointer">← 홈</button>
<div class="font-bold text-slate-800">
<span id="i18n-searchResults">검색결과</span>
:
@@ -478,6 +478,7 @@
};
safeSet('i18n-back', 'back');
safeSet('i18n-home', 'homeNav');
safeSet('i18n-searchResults', 'searchResults');
safeSet('i18n-resetBtn', 'resetSelection');
safeSet('i18n-labelSearch', 'search');
@@ -505,58 +506,89 @@
window.location.href = lang && lang !== 'ko' ? `/?lang=${lang}` : '/';
};
/**
* 동일 카드 번호 그룹 합계
* @param {string} cardNumber
* @returns {number}
*/
function getNumberGroupTotal(cardNumber) {
return cardList
.filter((c) => c.Number === cardNumber)
.reduce((sum, c) => sum + c.count, 0);
}
/**
* 동일 카드 번호 그룹 최대 수량
* @param {string} cardNumber
* @returns {number}
*/
function getGroupMaxCount(cardNumber) {
const cards = cardList.filter((c) => c.Number === cardNumber);
if (!cards.length) return 4;
return Math.max(...cards.map((c) => c.maxCount || 4));
}
/**
* 카드 컨테이너 조회
* @param {string} imgSrc
* @returns {HTMLElement|null}
*/
function getCardContainer(imgSrc) {
return document.querySelector(`.cardContainer[data-imgsrc="${CSS.escape(imgSrc)}"]`);
}
/**
* 동일 번호 카드들의 +/- UI 동기화
* @param {string} cardNumber
*/
function updateCountControlsForNumber(cardNumber) {
const groupTotal = getNumberGroupTotal(cardNumber);
const groupAtMax = groupTotal >= getGroupMaxCount(cardNumber);
cardList
.filter((c) => c.Number === cardNumber)
.forEach((sibling) => {
const container = getCardContainer(sibling.imgSrc);
if (!container) return;
const minusBtn = container.querySelector('.minusBtn');
const plusBtn = container.querySelector('.plusBtn');
const countSpan = container.querySelector('.countText');
const keyBtn = container.querySelector('.keyBtn');
if (countSpan) countSpan.textContent = sibling.count;
if (minusBtn) {
minusBtn.classList.toggle('invisible', sibling.count === 0);
minusBtn.classList.toggle('visible', sibling.count > 0);
}
if (plusBtn) {
plusBtn.classList.toggle('invisible', groupAtMax);
plusBtn.classList.toggle('visible', !groupAtMax);
}
if (keyBtn) {
keyBtn.className = `keyBtn w-full py-1 rounded text-[9px] lg:text-[10px] font-bold border transition-all ${sibling.key && sibling.count > 0 ? 'bg-indigo-600 border-indigo-600 text-white shadow-md' : 'bg-white border-slate-200 text-slate-400'}`;
}
});
}
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)) {
const maxCount = getGroupMaxCount(card.Number);
const currentTotal = getNumberGroupTotal(card.Number);
if (delta > 0 && currentTotal < maxCount) {
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'}`;
}
}
updateCountControlsForNumber(card.Number);
syncToStorageAndCounters();
};
@@ -585,7 +617,7 @@
};
function isMax(card) {
return cardList.filter((c) => c.Number === card.Number).reduce((s, c) => s + c.count, 0) >= (card.maxCount || 4);
return getNumberGroupTotal(card.Number) >= getGroupMaxCount(card.Number);
}
function updateCardList() {