Compare commits
4 Commits
main
...
resale-sit
| Author | SHA1 | Date | |
|---|---|---|---|
| b819238cfa | |||
| 38c49fc6e0 | |||
| 307ce212c2 | |||
| e947fda7ff |
6932
db/amiibo.resale.db.js
Normal file
6932
db/amiibo.resale.db.js
Normal file
File diff suppressed because it is too large
Load Diff
6929
db/amiibo.resale.db.json
Normal file
6929
db/amiibo.resale.db.json
Normal file
File diff suppressed because it is too large
Load Diff
910
index.html
910
index.html
@@ -1,215 +1,399 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<title>Amiibo Filter</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Amiibo Used Sale</title>
|
||||
<style>
|
||||
:root {
|
||||
color: #111827;
|
||||
background: #f8fafc;
|
||||
font-family:
|
||||
Arial,
|
||||
"Apple SD Gothic Neo",
|
||||
"Noto Sans KR",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
-webkit-text-decoration: inherit;
|
||||
text-decoration: inherit;
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 2rem;
|
||||
background: #f0f0f0;
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
h1 {
|
||||
margin-bottom: 1rem;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
/* 좌우 2단 레이아웃 */
|
||||
.container {
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.page {
|
||||
width: min(1440px, 100%);
|
||||
margin: 0 auto;
|
||||
padding: 28px 20px 48px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
/* height: calc(100vh - 4rem); */
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
align-items: flex-end;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding-bottom: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* 필터 영역 */
|
||||
.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%;
|
||||
.header h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: clamp(1.9rem, 4vw, 3rem);
|
||||
line-height: 1;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.header p {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.summary {
|
||||
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;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
/* 결과 영역 */
|
||||
.results {
|
||||
flex: 1 1 auto;
|
||||
.summary-pill {
|
||||
border: 1px solid #dbe4ef;
|
||||
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;
|
||||
padding: 8px 10px;
|
||||
font-size: 0.82rem;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
/* 목록 그리드 */
|
||||
#amiibo-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
.summary-pill strong {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: #0f172a;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill,minmax(220px,1fr));
|
||||
gap: 1rem;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
grid-template-columns: 280px minmax(0, 1fr);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
}
|
||||
#amiibo-list li {
|
||||
|
||||
.filters {
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.filter-title {
|
||||
margin: 0 0 8px;
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.filter-options {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.filter-option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
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;
|
||||
color: #334155;
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 0.3rem;
|
||||
text-align: center;
|
||||
line-height: 1.35;
|
||||
}
|
||||
#amiibo-list .status {
|
||||
font-size: 0.8rem;
|
||||
color: #333;
|
||||
|
||||
.filter-option input {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.field,
|
||||
.select {
|
||||
width: 100%;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
font-size: 0.95rem;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: 0;
|
||||
border-radius: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #4f46e5;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button.secondary {
|
||||
background: #e2e8f0;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.result-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.result-head strong {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 14px;
|
||||
width: 100%;
|
||||
max-width: 970px;
|
||||
}
|
||||
|
||||
.card {
|
||||
display: grid;
|
||||
grid-template-columns: 88px minmax(0, 1fr);
|
||||
gap: 14px;
|
||||
min-height: 180px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.card img {
|
||||
width: 88px;
|
||||
height: 120px;
|
||||
object-fit: contain;
|
||||
border-radius: 6px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.series {
|
||||
color: #64748b;
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price {
|
||||
color: #111827;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
border-radius: 999px;
|
||||
padding: 3px 8px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.badge.boxed {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.badge.opened {
|
||||
background: #e0f2fe;
|
||||
color: #075985;
|
||||
}
|
||||
|
||||
.badge.damaged {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
.meta {
|
||||
color: #475569;
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.45;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.meta strong {
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.empty {
|
||||
border: 1px solid #e5e7eb;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 36px;
|
||||
text-align: center;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
@media (max-width: 820px) {
|
||||
.header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.summary {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.filters {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.grid {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 520px) {
|
||||
.page {
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.result-head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.card {
|
||||
grid-template-columns: 68px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.card img {
|
||||
width: 68px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page">
|
||||
<header class="header">
|
||||
<div>
|
||||
<h1><a href="./index.html">Amiibo 보유 리스트</a></h1>
|
||||
</div>
|
||||
<div class="summary" id="summary"></div>
|
||||
</header>
|
||||
|
||||
<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 class="layout">
|
||||
<aside class="filters" aria-label="판매 필터">
|
||||
<div>
|
||||
<p class="filter-title">직접 검색</p>
|
||||
<input id="searchInput" class="field" type="search" placeholder="아미보명, 시리즈 검색" />
|
||||
</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>
|
||||
<p class="filter-title">정렬</p>
|
||||
<select id="sortSelect" class="select">
|
||||
<option value="priceDesc">판매가 높은순</option>
|
||||
<option value="priceAsc">판매가 낮은순</option>
|
||||
<option value="noDesc">최근 번호순</option>
|
||||
<option value="series">시리즈순</option>
|
||||
</select>
|
||||
</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>
|
||||
<p class="filter-title">제품 상태</p>
|
||||
<div class="filter-options" id="conditionOptions"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import AMIIBO_DB from './db/amiibo.db.js';
|
||||
<div>
|
||||
<p class="filter-title">가격 기준</p>
|
||||
<div class="filter-options" id="basisOptions"></div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="filter-title">시리즈</p>
|
||||
<div class="filter-options" id="seriesOptions"></div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button id="resetFilters" class="button secondary" type="button">필터 초기화</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section aria-live="polite">
|
||||
<div class="result-head">
|
||||
<div id="resultCount"></div>
|
||||
</div>
|
||||
<div id="amiiboList" class="grid"></div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script type="module">
|
||||
import AMIIBO_DB, { AMIIBO_RESALE_METADATA } from './db/amiibo.resale.db.js';
|
||||
|
||||
const SHOW_PRICE_RANGE = false;
|
||||
|
||||
// 시리즈명 일본어 → 한국어 변환 맵
|
||||
const seriesNameMap = {
|
||||
'大乱闘スマッシュブラザーズシリーズ': '대난투 스매시브라더스 시리즈',
|
||||
'スーパーマリオシリーズ': '슈퍼 마리오 시리즈',
|
||||
@@ -230,247 +414,227 @@
|
||||
'ロックマンシリーズ': '록맨 시리즈',
|
||||
'DARK SOULSシリーズ': '다크 소울 시리즈',
|
||||
'モンスターハンターシリーズ': '몬스터 헌터 시리즈',
|
||||
'others': '기타',
|
||||
'ゼノブレイドシリーズ': '제로브레이드 시리즈'
|
||||
'ゼノブレイドシリーズ': '제노블레이드 시리즈',
|
||||
others: '기타',
|
||||
};
|
||||
|
||||
// 현재 선택된 언어 (기본: ko)
|
||||
let currentLang = 'ko';
|
||||
const conditionLabels = {
|
||||
UNOPENED_OR_BOXED: '미개봉/박스 보존',
|
||||
OPENED_USED: '개봉 중고',
|
||||
DAMAGED: '손상/파손',
|
||||
};
|
||||
|
||||
// 필터 상태 저장
|
||||
const selectedFilters = {
|
||||
const basisLabels = {
|
||||
KR_USED_REFERENCE_ESTIMATE: '국내 중고 시세 참고',
|
||||
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '일본 중고 시세 환산',
|
||||
US_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '미국 중고 시세 환산',
|
||||
};
|
||||
|
||||
const selected = {
|
||||
condition: new Set(),
|
||||
basis: new Set(),
|
||||
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 searchInput = document.getElementById('searchInput');
|
||||
const sortSelect = document.getElementById('sortSelect');
|
||||
const conditionOptions = document.getElementById('conditionOptions');
|
||||
const basisOptions = document.getElementById('basisOptions');
|
||||
const seriesOptions = document.getElementById('seriesOptions');
|
||||
const summary = document.getElementById('summary');
|
||||
const resultCount = document.getElementById('resultCount');
|
||||
const amiiboList = document.getElementById('amiiboList');
|
||||
|
||||
// 상태 필터 옵션
|
||||
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 formatKRW(value) {
|
||||
if (typeof value !== 'number') return '-';
|
||||
return `${value.toLocaleString('ko-KR')}원`;
|
||||
}
|
||||
|
||||
// 상태 필터 렌더링 함수
|
||||
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;
|
||||
function getTitle(item) {
|
||||
return item.koTitle || item.usTitle || item.title;
|
||||
}
|
||||
|
||||
div.appendChild(input);
|
||||
div.appendChild(label);
|
||||
statusContainer.appendChild(div);
|
||||
});
|
||||
function getSeriesLabel(series) {
|
||||
return seriesNameMap[series] || series;
|
||||
}
|
||||
|
||||
const amiiboCount = document.getElementById('amiibo-count'); // 상단에 추가
|
||||
function getConditionLabel(item) {
|
||||
return conditionLabels[item.condition?.saleCondition] || '상태 확인 필요';
|
||||
}
|
||||
|
||||
function renderAmiiboList() {
|
||||
let filtered = AMIIBO_DB.filter(a => {
|
||||
if (selectedFilters.series.size > 0 && !selectedFilters.series.has(a.series)) {
|
||||
function getConditionClass(item) {
|
||||
if (item.condition?.isDamaged) return 'damaged';
|
||||
if (item.condition?.isOpen) return 'opened';
|
||||
return 'boxed';
|
||||
}
|
||||
|
||||
function getBasisLabel(item) {
|
||||
return basisLabels[item.sale?.pricingBasis] || '가격 기준 확인 필요';
|
||||
}
|
||||
|
||||
function renderSummary() {
|
||||
summary.innerHTML = `
|
||||
<div class="summary-pill">상품 수<strong>${AMIIBO_RESALE_METADATA.itemCount.toLocaleString('ko-KR')}종</strong></div>
|
||||
<div class="summary-pill">총 수량<strong>${AMIIBO_RESALE_METADATA.quantityTotal.toLocaleString('ko-KR')}개</strong></div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderCheckboxes(container, entries, group) {
|
||||
container.innerHTML = entries
|
||||
.map(
|
||||
([value, label]) => `
|
||||
<label class="filter-option">
|
||||
<input type="checkbox" value="${value}" data-filter-group="${group}" />
|
||||
<span>${label}</span>
|
||||
</label>
|
||||
`,
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
|
||||
function renderFilters() {
|
||||
renderCheckboxes(
|
||||
conditionOptions,
|
||||
Object.entries(conditionLabels),
|
||||
'condition',
|
||||
);
|
||||
renderCheckboxes(basisOptions, Object.entries(basisLabels), 'basis');
|
||||
|
||||
const seriesEntries = [...new Set(AMIIBO_DB.map(item => item.series))]
|
||||
.sort((a, b) => getSeriesLabel(a).localeCompare(getSeriesLabel(b), 'ko-KR'))
|
||||
.map(series => [series, getSeriesLabel(series)]);
|
||||
renderCheckboxes(seriesOptions, seriesEntries, 'series');
|
||||
}
|
||||
|
||||
function getFilteredItems() {
|
||||
const query = searchInput.value.trim().toLowerCase();
|
||||
|
||||
return AMIIBO_DB.filter(item => {
|
||||
if (query) {
|
||||
const target = [getTitle(item), item.title, item.usTitle, getSeriesLabel(item.series), item.series]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase();
|
||||
if (!target.includes(query)) return false;
|
||||
}
|
||||
|
||||
if (
|
||||
selected.condition.size > 0 &&
|
||||
!selected.condition.has(item.condition?.saleCondition)
|
||||
) {
|
||||
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;
|
||||
if (selected.basis.size > 0 && !selected.basis.has(item.sale?.pricingBasis)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selected.series.size > 0 && !selected.series.has(item.series)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
// 정렬: no 기준 내림차순
|
||||
filtered.sort((a, b) => b.no - a.no);
|
||||
function sortItems(items) {
|
||||
const sortBy = sortSelect.value;
|
||||
const sorted = [...items];
|
||||
|
||||
// 카운트 텍스트 설정
|
||||
const countText = currentLang === 'ko'
|
||||
? `총 ${filtered.length}개`
|
||||
: `全${filtered.length}件`;
|
||||
amiiboCount.textContent = countText;
|
||||
if (sortBy === 'priceDesc') {
|
||||
return sorted.sort((a, b) => b.sale.suggestedPrice - a.sale.suggestedPrice || b.no - a.no);
|
||||
}
|
||||
|
||||
if (sortBy === 'priceAsc') {
|
||||
return sorted.sort((a, b) => a.sale.suggestedPrice - b.sale.suggestedPrice || b.no - a.no);
|
||||
}
|
||||
|
||||
if (sortBy === 'series') {
|
||||
return sorted.sort(
|
||||
(a, b) =>
|
||||
getSeriesLabel(a.series).localeCompare(getSeriesLabel(b.series), 'ko-KR') ||
|
||||
b.sale.suggestedPrice - a.sale.suggestedPrice,
|
||||
);
|
||||
}
|
||||
|
||||
return sorted.sort((a, b) => b.no - a.no);
|
||||
}
|
||||
|
||||
function renderList() {
|
||||
const filtered = sortItems(getFilteredItems());
|
||||
|
||||
resultCount.innerHTML = `<strong>${filtered.length.toLocaleString('ko-KR')}종</strong> 표시 중`;
|
||||
|
||||
// 결과 없을 경우
|
||||
if (filtered.length === 0) {
|
||||
amiiboList.innerHTML = currentLang === 'ko'
|
||||
? '<li>검색 결과가 없습니다.</li>'
|
||||
: '<li>検索結果がありません。</li>';
|
||||
amiiboList.innerHTML = '<div class="empty">조건에 맞는 아미보가 없습니다.</div>';
|
||||
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' ? '정상' : '正常';
|
||||
amiiboList.innerHTML = filtered
|
||||
.map(item => {
|
||||
const range = item.sale.priceRange;
|
||||
const priceRangeHtml = SHOW_PRICE_RANGE
|
||||
? `<div class="meta"><strong>가격 범위</strong> ${formatKRW(range.min)} ~ ${formatKRW(range.max)}</div>`
|
||||
: '';
|
||||
const notes = item.sale.conditionNotes?.length
|
||||
? `<div class="meta">${item.sale.conditionNotes.join(', ')}</div>`
|
||||
: '';
|
||||
|
||||
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}
|
||||
<article class="card">
|
||||
<img src="${item.image}" alt="${getTitle(item)}" loading="lazy" />
|
||||
<div class="card-body">
|
||||
<div>
|
||||
<h2 class="title">${item.no}. ${getTitle(item)}</h2>
|
||||
<div class="series">${getSeriesLabel(item.series)}</div>
|
||||
</div>
|
||||
</li>
|
||||
<div class="price-row">
|
||||
<span class="price">${formatKRW(item.sale.suggestedPrice)}</span>
|
||||
<span class="badge ${getConditionClass(item)}">${getConditionLabel(item)}</span>
|
||||
</div>
|
||||
${priceRangeHtml}
|
||||
<div class="meta"><strong>가격 기준</strong> ${getBasisLabel(item)}</div>
|
||||
<div class="meta"><strong>수량</strong> ${item.count.toLocaleString('ko-KR')}개 · <strong>소계</strong> ${formatKRW(item.sale.totalAssetValue)}</div>
|
||||
${notes}
|
||||
</div>
|
||||
</article>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
// 필터 초기화 함수
|
||||
function resetFilters() {
|
||||
selectedFilters.series.clear();
|
||||
selectedFilters.status.clear();
|
||||
document.querySelectorAll('input[type=checkbox]').forEach(chk => (chk.checked = false));
|
||||
renderAmiiboList();
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
// 셔플 함수
|
||||
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 handleFilterChange(event) {
|
||||
const input = event.target;
|
||||
const group = input.dataset.filterGroup;
|
||||
if (!group) return;
|
||||
|
||||
// 언어 전환 버튼 클릭 처리
|
||||
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');
|
||||
if (input.checked) {
|
||||
selected[group].add(input.value);
|
||||
} else {
|
||||
btnJa.classList.add('active');
|
||||
btnJa.setAttribute('aria-pressed', 'true');
|
||||
btnKo.classList.remove('active');
|
||||
btnKo.setAttribute('aria-pressed', 'false');
|
||||
selected[group].delete(input.value);
|
||||
}
|
||||
|
||||
// 필터 라벨 재렌더링
|
||||
renderSeriesFilters();
|
||||
renderStatusFilters();
|
||||
|
||||
// 리스트 재렌더링
|
||||
renderAmiiboList();
|
||||
renderList();
|
||||
}
|
||||
|
||||
// 초기 렌더링
|
||||
renderSeriesFilters();
|
||||
renderStatusFilters();
|
||||
renderAmiiboList();
|
||||
function resetFilters() {
|
||||
Object.values(selected).forEach(values => values.clear());
|
||||
document.querySelectorAll('[data-filter-group]').forEach(input => {
|
||||
input.checked = false;
|
||||
});
|
||||
searchInput.value = '';
|
||||
sortSelect.value = 'priceDesc';
|
||||
renderList();
|
||||
}
|
||||
|
||||
// 이벤트 리스너 연결
|
||||
document.getElementById('resetFilter').addEventListener('click', resetFilters);
|
||||
document.getElementById('shuffle').addEventListener('click', shuffleAmiibos);
|
||||
btnKo.addEventListener('click', () => setLanguage('ko'));
|
||||
btnJa.addEventListener('click', () => setLanguage('ja'));
|
||||
renderSummary();
|
||||
renderFilters();
|
||||
renderList();
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
searchInput.addEventListener('input', renderList);
|
||||
sortSelect.addEventListener('change', renderList);
|
||||
document.querySelector('.filters').addEventListener('change', handleFilterChange);
|
||||
document.getElementById('resetFilters').addEventListener('click', resetFilters);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user