477 lines
15 KiB
HTML
477 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
|
|
<title>Amiibo Filter</title>
|
|
<style>
|
|
|
|
a {
|
|
color: inherit;
|
|
-webkit-text-decoration: inherit;
|
|
text-decoration: inherit;
|
|
}
|
|
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
padding: 2rem;
|
|
background: #f0f0f0;
|
|
margin: 0;
|
|
height: 100vh;
|
|
box-sizing: border-box;
|
|
}
|
|
h1 {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* 좌우 2단 레이아웃 */
|
|
.container {
|
|
display: flex;
|
|
gap: 2rem;
|
|
/* height: calc(100vh - 4rem); */
|
|
}
|
|
|
|
/* 필터 영역 */
|
|
.filters {
|
|
flex: 0 0 280px;
|
|
background: #fff;
|
|
padding: 1rem;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
|
overflow-y: auto;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
/* 언어 선택 버튼 영역 */
|
|
.language-switch {
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
justify-content: center;
|
|
}
|
|
.language-switch button {
|
|
flex: 1;
|
|
padding: 0.5rem 0;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-weight: bold;
|
|
background-color: #5c6ac4;
|
|
color: white;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
.language-switch button.active,
|
|
.language-switch button:hover {
|
|
background-color: #4b57a0;
|
|
}
|
|
|
|
/* 필터 그룹 */
|
|
.filter-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
.filter-title {
|
|
font-weight: bold;
|
|
margin-bottom: 0.5rem;
|
|
font-size: 1.1rem;
|
|
border-bottom: 1px solid #ccc;
|
|
padding-bottom: 0.25rem;
|
|
}
|
|
.filter-option {
|
|
margin-bottom: 0.4rem;
|
|
}
|
|
|
|
/* 버튼들 */
|
|
.buttons {
|
|
display: flex;
|
|
gap: 1rem;
|
|
margin-top: auto; /* 하단에 붙도록 */
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
}
|
|
button#resetFilter, button#shuffle {
|
|
cursor: pointer;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 5px;
|
|
border: none;
|
|
background: #5c6ac4;
|
|
color: white;
|
|
font-weight: bold;
|
|
transition: background-color 0.3s ease;
|
|
flex: 1 1 100px;
|
|
}
|
|
button#resetFilter:hover, button#shuffle:hover {
|
|
background: #4b57a0;
|
|
}
|
|
|
|
/* 결과 영역 */
|
|
.results {
|
|
flex: 1 1 auto;
|
|
background: #fff;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
|
padding: 1rem;
|
|
overflow-y: auto;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.results h2 {
|
|
margin-top: 0;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* 목록 그리드 */
|
|
#amiibo-list {
|
|
list-style: none;
|
|
padding-left: 0;
|
|
margin: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill,minmax(220px,1fr));
|
|
gap: 1rem;
|
|
flex-grow: 1;
|
|
overflow-y: auto;
|
|
}
|
|
#amiibo-list li {
|
|
background: #fff;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
|
padding: 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
#amiibo-list li.not-have {
|
|
opacity: 0.5;
|
|
}
|
|
#amiibo-list img {
|
|
max-width: 150px;
|
|
border-radius: 4px;
|
|
margin-bottom: 0.5rem;
|
|
object-fit: contain;
|
|
}
|
|
#amiibo-list .title {
|
|
font-weight: bold;
|
|
margin-bottom: 0.3rem;
|
|
text-align: center;
|
|
word-break: break-word;
|
|
}
|
|
#amiibo-list .subtitle {
|
|
color: #555;
|
|
font-size: 0.9rem;
|
|
margin-bottom: 0.3rem;
|
|
text-align: center;
|
|
}
|
|
#amiibo-list .status {
|
|
font-size: 0.8rem;
|
|
color: #333;
|
|
text-align: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1><a href="./index.html">Amiibo DB</a></h1>
|
|
|
|
<div class="container">
|
|
<!-- 좌측 필터 -->
|
|
<div class="filters" role="region" aria-label="Amiibo Filters">
|
|
|
|
<!-- 언어 선택 버튼 -->
|
|
<div class="language-switch" role="group" aria-label="언어 선택">
|
|
<button type="button" id="btn-ko" class="active" aria-pressed="true">한국어</button>
|
|
<button type="button" id="btn-ja" aria-pressed="false">日本語</button>
|
|
</div>
|
|
|
|
<div class="filter-group" id="series-filter">
|
|
<div class="filter-title">Series</div>
|
|
<div id="series-options"></div>
|
|
</div>
|
|
<div class="filter-group" id="status-filter">
|
|
<div class="filter-title">Status</div>
|
|
<div id="status-options"></div>
|
|
</div>
|
|
<div class="buttons">
|
|
<button id="resetFilter" type="button">필터 초기화</button>
|
|
<button id="shuffle" type="button">SUFFLE!</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 우측 결과 -->
|
|
<div class="results" role="main">
|
|
<div id="amiibo-count" style="margin-bottom: 1rem; font-weight: bold;"></div>
|
|
<ul id="amiibo-list" tabindex="0" aria-live="polite" aria-relevant="all"></ul>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="module">
|
|
import AMIIBO_DB from './db/amiibo.db.js';
|
|
|
|
// 시리즈명 일본어 → 한국어 변환 맵
|
|
const seriesNameMap = {
|
|
'大乱闘スマッシュブラザーズシリーズ': '대난투 스매시브라더스 시리즈',
|
|
'スーパーマリオシリーズ': '슈퍼 마리오 시리즈',
|
|
'スプラトゥーンシリーズ': '스플래툰 시리즈',
|
|
'ヨッシー ウールワールドシリーズ': '요시 울월드 시리즈',
|
|
'SUPER MARIO BROS. 30thシリーズ': '슈퍼 마리오 브라더스 30주년 시리즈',
|
|
'ちびロボ!シリーズ': '치비로보! 시리즈',
|
|
'どうぶつの森シリーズ': '동물의 숲 시리즈',
|
|
'ゼルダの伝説シリーズ': '젤다의 전설 시리즈',
|
|
'星のカービィシリーズ': '별의 커비 시리즈',
|
|
'ショベルナイトシリーズ': '쇼벨 나이트 시리즈',
|
|
'モンスターハンター ストーリーズシリーズ': '몬스터 헌터 스토리즈 시리즈',
|
|
'ハコボーイ!シリーズ': '하코보이! 시리즈',
|
|
'ファイアーエムブレムシリーズ': '파이어 엠블렘 시리즈',
|
|
'ピクミンシリーズ': '피크민 시리즈',
|
|
'メトロイドシリーズ': '메트로이드 시리즈',
|
|
'ポケモンシリーズ': '포켓몬 시리즈',
|
|
'ロックマンシリーズ': '록맨 시리즈',
|
|
'DARK SOULSシリーズ': '다크 소울 시리즈',
|
|
'モンスターハンターシリーズ': '몬스터 헌터 시리즈',
|
|
'others': '기타',
|
|
'ゼノブレイドシリーズ': '제로브레이드 시리즈'
|
|
};
|
|
|
|
// 현재 선택된 언어 (기본: ko)
|
|
let currentLang = 'ko';
|
|
|
|
// 필터 상태 저장
|
|
const selectedFilters = {
|
|
series: new Set(),
|
|
status: new Set()
|
|
};
|
|
|
|
// 시리즈 필터 옵션 생성 (중복제거 후)
|
|
const seriesOptions = [...new Set(AMIIBO_DB.map(a => a.series))].map(series => ({
|
|
value: series,
|
|
label: seriesNameMap[series] || series
|
|
}));
|
|
|
|
// 상태 필터 옵션
|
|
const statusFilters = [
|
|
{ value: 'isHave', label: '보유' },
|
|
{ value: 'notHave', label: '미보유' },
|
|
{ value: 'isOpen', label: '개봉' },
|
|
{ value: 'notOpen', label: '미개봉' },
|
|
{ value: 'isDamaged', label: '박스훼손' }
|
|
];
|
|
|
|
// DOM 요소 참조
|
|
const seriesContainer = document.getElementById('series-options');
|
|
const statusContainer = document.getElementById('status-options');
|
|
const amiiboList = document.getElementById('amiibo-list');
|
|
const btnKo = document.getElementById('btn-ko');
|
|
const btnJa = document.getElementById('btn-ja');
|
|
|
|
// 시리즈 필터 렌더링 함수
|
|
function renderSeriesFilters() {
|
|
seriesContainer.innerHTML = '';
|
|
seriesOptions.forEach(opt => {
|
|
const div = document.createElement('div');
|
|
div.className = 'filter-option';
|
|
|
|
const input = document.createElement('input');
|
|
input.type = 'checkbox';
|
|
input.id = `series-${opt.value}`;
|
|
input.value = opt.value;
|
|
|
|
input.addEventListener('change', () => {
|
|
if (input.checked) {
|
|
selectedFilters.series.add(opt.value);
|
|
} else {
|
|
selectedFilters.series.delete(opt.value);
|
|
}
|
|
renderAmiiboList();
|
|
});
|
|
|
|
const label = document.createElement('label');
|
|
label.htmlFor = input.id;
|
|
label.textContent = (currentLang === 'ko') ? (seriesNameMap[opt.value] || opt.value) : opt.value;
|
|
|
|
div.appendChild(input);
|
|
div.appendChild(label);
|
|
seriesContainer.appendChild(div);
|
|
});
|
|
}
|
|
|
|
// 상태 필터 렌더링 함수
|
|
function renderStatusFilters() {
|
|
statusContainer.innerHTML = '';
|
|
statusFilters.forEach(opt => {
|
|
const div = document.createElement('div');
|
|
div.className = 'filter-option';
|
|
|
|
const input = document.createElement('input');
|
|
input.type = 'checkbox';
|
|
input.id = `status-${opt.value}`;
|
|
input.value = opt.value;
|
|
|
|
input.addEventListener('change', () => {
|
|
if (input.checked) {
|
|
selectedFilters.status.add(opt.value);
|
|
} else {
|
|
selectedFilters.status.delete(opt.value);
|
|
}
|
|
renderAmiiboList();
|
|
});
|
|
|
|
const label = document.createElement('label');
|
|
label.htmlFor = input.id;
|
|
|
|
// 상태명도 언어별로 변경 (일본어는 단순 예시)
|
|
if (currentLang === 'ko') {
|
|
label.textContent = opt.label;
|
|
} else {
|
|
const jaLabels = {
|
|
'보유': '保有',
|
|
'미보유': '未保有',
|
|
'개봉': '開封',
|
|
'미개봉': '未開封',
|
|
'박스훼손': '箱破損'
|
|
};
|
|
label.textContent = jaLabels[opt.label] || opt.label;
|
|
}
|
|
|
|
div.appendChild(input);
|
|
div.appendChild(label);
|
|
statusContainer.appendChild(div);
|
|
});
|
|
}
|
|
|
|
const amiiboCount = document.getElementById('amiibo-count'); // 상단에 추가
|
|
|
|
function renderAmiiboList() {
|
|
let filtered = AMIIBO_DB.filter(a => {
|
|
if (selectedFilters.series.size > 0 && !selectedFilters.series.has(a.series)) {
|
|
return false;
|
|
}
|
|
|
|
if (selectedFilters.status.size > 0) {
|
|
const statusArr = Array.from(selectedFilters.status);
|
|
const statusMatch = statusArr.every(status => {
|
|
if (status === 'isHave') return a.status.isHave === true;
|
|
if (status === 'notHave') return a.status.isHave === false;
|
|
if (status === 'isOpen') return a.status.isOpen === true;
|
|
if (status === 'notOpen') return a.status.isHave === true && a.status.isOpen === false;
|
|
if (status === 'isDamaged') return a.status.isDamaged === true;
|
|
return true;
|
|
});
|
|
if (!statusMatch) return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// 정렬: no 기준 내림차순
|
|
filtered.sort((a, b) => b.no - a.no);
|
|
|
|
// 카운트 텍스트 설정
|
|
const countText = currentLang === 'ko'
|
|
? `총 ${filtered.length}개`
|
|
: `全${filtered.length}件`;
|
|
amiiboCount.textContent = countText;
|
|
|
|
// 결과 없을 경우
|
|
if (filtered.length === 0) {
|
|
amiiboList.innerHTML = currentLang === 'ko'
|
|
? '<li>검색 결과가 없습니다.</li>'
|
|
: '<li>検索結果がありません。</li>';
|
|
return;
|
|
}
|
|
|
|
amiiboList.innerHTML = filtered.map(a => {
|
|
const title = currentLang === 'ko' ? (a.koTitle || a.title) : (a.usTitle || a.title);
|
|
const series = currentLang === 'ko' ? (seriesNameMap[a.series] || a.series) : a.series;
|
|
const haveText = currentLang === 'ko' ? '보유' : '保有';
|
|
const notHaveText = currentLang === 'ko' ? '미보유' : '未保有';
|
|
const openText = currentLang === 'ko' ? '개봉' : '開封';
|
|
const notOpenText = currentLang === 'ko' ? '미개봉' : '未開封';
|
|
const damagedText = currentLang === 'ko' ? '박스훼손' : '箱破損';
|
|
const normalText = currentLang === 'ko' ? '정상' : '正常';
|
|
|
|
return `
|
|
<li class="amiibo-card ${a.status.isHave ? '' : 'not-have'}" style="position: relative;">
|
|
<span style="position: absolute; top: 6px; left: 8px; font-size: 0.75rem; color: #666;">no.${a.no}</span>
|
|
${a.status.isHave ? `<span style="position: absolute; top: 6px; right: 8px; font-size: 0.75rem; color: #666;">x${a.count}</span>` : ''}
|
|
<img src="${a.image}" alt="${title}" loading="lazy" />
|
|
<div class="title">${title}</div>
|
|
<div class="subtitle">${series}</div>
|
|
<div class="status">
|
|
${a.status.isHave ? haveText : notHaveText} /
|
|
${a.status.isOpen ? openText : notOpenText} /
|
|
${a.status.isDamaged ? damagedText : normalText}
|
|
</div>
|
|
</li>
|
|
`;
|
|
}).join('');
|
|
}
|
|
// 필터 초기화 함수
|
|
function resetFilters() {
|
|
selectedFilters.series.clear();
|
|
selectedFilters.status.clear();
|
|
document.querySelectorAll('input[type=checkbox]').forEach(chk => (chk.checked = false));
|
|
renderAmiiboList();
|
|
}
|
|
|
|
// 셔플 함수
|
|
function shuffleAmiibos() {
|
|
const shuffled = [...AMIIBO_DB].sort(() => Math.random() - 0.5);
|
|
amiiboList.innerHTML = shuffled.map(a => `
|
|
<li class="${a.status.isHave ? '' : 'not-have'}">
|
|
<img src="${a.image}" alt="${currentLang === 'ko' ? (a.koTitle || a.title) : (a.usTitle || a.title)}" loading="lazy" />
|
|
<div class="title">${currentLang === 'ko' ? (a.koTitle || a.title) : (a.usTitle || a.title)}</div>
|
|
<div class="subtitle">${currentLang === 'ko' ? (seriesNameMap[a.series] || a.series) : a.series}</div>
|
|
<div class="status">
|
|
${a.status.isHave ? (currentLang === 'ko' ? '보유' : '保有') : (currentLang === 'ko' ? '미보유' : '未保有')} /
|
|
${a.status.isOpen ? (currentLang === 'ko' ? '개봉' : '開封') : (currentLang === 'ko' ? '미개봉' : '未開封')} /
|
|
${a.status.isDamaged ? (currentLang === 'ko' ? '박스훼손' : '箱破損') : (currentLang === 'ko' ? '정상' : '正常')}
|
|
</div>
|
|
</li>
|
|
`).join('');
|
|
}
|
|
|
|
// 언어 전환 버튼 클릭 처리
|
|
function setLanguage(lang) {
|
|
if (lang === currentLang) return;
|
|
|
|
currentLang = lang;
|
|
|
|
// 버튼 상태 변경
|
|
if (lang === 'ko') {
|
|
btnKo.classList.add('active');
|
|
btnKo.setAttribute('aria-pressed', 'true');
|
|
btnJa.classList.remove('active');
|
|
btnJa.setAttribute('aria-pressed', 'false');
|
|
} else {
|
|
btnJa.classList.add('active');
|
|
btnJa.setAttribute('aria-pressed', 'true');
|
|
btnKo.classList.remove('active');
|
|
btnKo.setAttribute('aria-pressed', 'false');
|
|
}
|
|
|
|
// 필터 라벨 재렌더링
|
|
renderSeriesFilters();
|
|
renderStatusFilters();
|
|
|
|
// 리스트 재렌더링
|
|
renderAmiiboList();
|
|
}
|
|
|
|
// 초기 렌더링
|
|
renderSeriesFilters();
|
|
renderStatusFilters();
|
|
renderAmiiboList();
|
|
|
|
// 이벤트 리스너 연결
|
|
document.getElementById('resetFilter').addEventListener('click', resetFilters);
|
|
document.getElementById('shuffle').addEventListener('click', shuffleAmiibos);
|
|
btnKo.addEventListener('click', () => setLanguage('ko'));
|
|
btnJa.addEventListener('click', () => setLanguage('ja'));
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|