v2026.03.30-04 레포지토리 재연결

프로젝트 초기 상태를 기준으로 저장소를 재구성하고 업로드 규칙을 정비한다.
이미지 자산은 Git LFS를 통해 원격 용량 제한에 대응한다.

Made-with: Cursor
This commit is contained in:
2026-03-30 12:38:03 +09:00
commit 95d092fbc2
545 changed files with 27203 additions and 0 deletions

173
script/nsw-detail.js Normal file
View File

@@ -0,0 +1,173 @@
import NSW_DB from '../db/nsw.db.js';
document.addEventListener('DOMContentLoaded', () => {
// URL에서 게임 번호 가져오기
const urlParams = new URLSearchParams(window.location.search);
const gameNo = parseInt(urlParams.get('no'));
// 게임 데이터 찾기
const game = NSW_DB.find(g => g.no === gameNo);
if (!game) {
alert('게임을 찾을 수 없습니다.');
window.location.href = 'index.html';
return;
}
// 언어 설정
const language = localStorage.getItem('language') || 'ko';
const texts = {
ko: {
infoTitle: '게임 정보',
purchaseTitle: '구매 정보',
requiredCapacity: '필요한 용량',
playMode: '플레이 모드',
playUser: '플레이 인원',
localPlayUser: '로컬 통신',
onlinePlayUser: '인터넷 통신',
compatibleController: '대응 컨트롤러',
onlineDataSave: '세이브 데이터 보관',
maker: '메이커',
language: '지원 언어',
cero: '심의 등급',
iarc: '심의 등급',
releaseDate: '출시일',
purchaseDate: '구매일',
store: '구매처',
price: '구매 가격',
orderNumber: '주문번호',
extension: '추가 콘텐츠',
none: '없음',
supported: '대응',
notSupported: '비대응',
},
ja: {
infoTitle: 'Infomation',
purchaseTitle: '購入情報',
requiredCapacity: '必要な容量',
playMode: 'プレイモード',
playUser: 'プレイ人数',
localPlayUser: 'ローカル通信',
onlinePlayUser: 'インターネット通信',
compatibleController: '対応コントローラー',
onlineDataSave: 'セーブデータお預かり',
maker: 'メーカー',
language: '対応言語',
cero: 'CERO',
iarc: 'IARC',
releaseDate: '配信日',
purchaseDate: '購入日',
store: '購入先',
price: '購入価格',
orderNumber: '注文番号',
extension: '追加コンテンツ',
none: 'なし',
supported: '対応',
notSupported: '非対応',
},
};
const currentTexts = texts[language];
// 기본 정보 설정
document.getElementById('gameImage').src = window.innerWidth < 640 ? game.thumbnail : game.image;
document.getElementById('gameTitle').textContent =
language === 'ko' ? game.koTitle || game.title : game.title;
document.getElementById('gameTags').textContent = game.tags;
document.getElementById('infoTitle').textContent = currentTexts.infoTitle;
document.getElementById('purchaseTitle').textContent = currentTexts.purchaseTitle;
document.getElementById('purchaseGameTitle').textContent =
language === 'ko' ? game.koTitle || game.title : game.title;
// 게임 상태 설정
const statusClass = {
package: 'bg-green-100 text-green-800',
download: 'bg-yellow-100 text-yellow-800',
expansion: 'bg-blue-100 text-blue-800',
}[game.status];
document.getElementById(
'gameStatus',
).className = `inline-flex rounded-full px-2 text-xs font-semibold leading-5 ${statusClass}`;
document.getElementById('gameStatus').textContent = game.status;
// 국가 설정
const countryClass =
game.country === 'JPN'
? 'text-red-600 hover:text-red-900'
: 'text-indigo-600 hover:text-indigo-900';
document.getElementById('gameCountry').className = `text-center ${countryClass}`;
document.getElementById('gameCountry').textContent =
language === 'ko'
? game.country === 'JPN'
? '일본판'
: '한국판'
: game.country === 'JPN'
? '日本版'
: '韓国版';
// 게임 정보 설정
const gameInfo = document.getElementById('gameInfo');
const infoItems = [
{ key: 'requiredCapacity', label: currentTexts.requiredCapacity },
{ key: 'playMode', label: currentTexts.playMode },
{ key: 'playUser', label: currentTexts.playUser },
{ key: 'compatibleController', label: currentTexts.compatibleController },
{ key: 'onlineDataSave', label: currentTexts.onlineDataSave },
{ key: 'maker', label: currentTexts.maker },
{ key: 'language', label: currentTexts.language },
{ key: 'cero', label: currentTexts.cero },
{ key: 'iarc', label: currentTexts.iarc },
{ key: 'releaseDate', label: currentTexts.releaseDate },
];
infoItems.forEach(item => {
if (game[item.key]) {
const div = document.createElement('div');
div.className = 'border-t border-gray-200 pt-4';
div.innerHTML = `
<dt class="font-medium text-gray-900">${item.label}</dt>
<dd class="mt-2 text-sm text-gray-500">${game[item.key]}</dd>
`;
gameInfo.appendChild(div);
}
});
// 구매 정보 설정
const purchaseInfo = document.getElementById('purchaseInfo');
if (game.purchaseInformation) {
const purchaseItems = [
{ key: 'date', label: currentTexts.purchaseDate },
{ key: 'store', label: currentTexts.store },
{ key: 'price', label: currentTexts.price },
{ key: 'orderNumber', label: currentTexts.orderNumber },
];
const dl = document.createElement('dl');
dl.className = 'grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2';
purchaseItems.forEach(item => {
if (game.purchaseInformation[item.key]) {
const div = document.createElement('div');
div.className = 'sm:col-span-1';
div.innerHTML = `
<dt class="text-sm font-medium text-gray-500">${item.label}</dt>
<dd class="mt-1 text-sm text-gray-900">${game.purchaseInformation[item.key]}</dd>
`;
dl.appendChild(div);
}
});
if (game.extension) {
const div = document.createElement('div');
div.className = 'sm:col-span-2';
div.innerHTML = `
<dt class="text-sm font-medium text-gray-500">${currentTexts.extension}</dt>
<dd class="mt-1 text-sm text-gray-900">
${game.extension.map(ext => `<div>${ext}</div>`).join('')}
</dd>
`;
dl.appendChild(div);
}
purchaseInfo.appendChild(dl);
}
});

943
script/nsw.js Normal file
View File

@@ -0,0 +1,943 @@
import NSW_DB from '../db/nsw.db.js';
document.addEventListener('DOMContentLoaded', () => {
const gameList = document.getElementById('gameList');
const gameCount = document.getElementById('gameCount');
const loading = document.getElementById('loading');
const languageRadios = document.querySelectorAll('input[name="language"]');
// 언어별 UI 텍스트
const uiTexts = {
ko: {
title: 'Switch DB',
languageSelect: '언어 선택',
languageDescription: '선택한 언어로 표시됩니다',
korean: '한국어',
japanese: '일본어',
count: '개수',
loading: '로딩중...',
tableHeaders: {
title: '제목',
info: '정보',
status: '상태',
role: '역할',
location: '지역'
},
sortOptions: {
sortByNoDesc: '순번 최신순',
sortByNo: '순번 과거순',
sortByDateDesc: '발매일 최신순',
sortByDate: '발매일 과거순',
sortByPurchaseDateDesc: '구매일 최신순',
sortByPurchaseDate: '구매일 과거순',
sortByRandom: '무작위',
},
filter: {
reset: '필터 초기화',
koreanSupport: '한국어 지원',
koreanNotSupport: '한국어 미지원',
},
},
ja: {
title: 'ゲーム一覧',
languageSelect: '言語選択',
languageDescription: '選択した言語で表示されます',
korean: '韓国語',
japanese: '日本語',
count: '件数',
loading: '読み込み中...',
tableHeaders: {
title: 'タイトル',
info: '情報',
status: '状態',
role: '役割',
location: '地域'
},
sortOptions: {
sortByNoDesc: '番号降順',
sortByNo: '番号昇順',
sortByDateDesc: '発売日降順',
sortByDate: '発売日昇順',
sortByPurchaseDateDesc: '購入日降順',
sortByPurchaseDate: '購入日昇順',
sortByRandom: 'ランダム',
},
filter: {
reset: 'フィルターリセット',
koreanSupport: '韓国語対応',
koreanNotSupport: '韓国語非対応',
},
},
};
// 필터 상태 관리 객체
const filterState = {
language: 'ko',
filters: {
language: [],
status: [],
country: [],
cero: [],
},
sortBy: 'sortByNoDesc', // 기본 정렬 옵션
};
// 정렬 옵션 정의
const sortOptions = [
{ name: '순번 최신순', value: 'sortByNoDesc' },
{ name: '순번 과거순', value: 'sortByNo' },
{ name: '발매일 최신순', value: 'sortByDateDesc' },
{ name: '발매일 과거순', value: 'sortByDate' },
{ name: '구매일 최신순', value: 'sortByPurchaseDateDesc' },
{ name: '구매일 과거순', value: 'sortByPurchaseDate' },
{ name: '무작위', value: 'sortByRandom' },
];
// UI 텍스트 업데이트 함수
function updateUITexts() {
const texts = uiTexts[filterState.language];
// 제목 업데이트
document.querySelector('h1').textContent = texts.title;
// 언어 선택 섹션 업데이트
document.querySelector('label.text-base').textContent = texts.languageSelect;
document.querySelector('p.text-sm').textContent = texts.languageDescription;
document.querySelector('label[for="ko"]').textContent = texts.korean;
document.querySelector('label[for="ja"]').textContent = texts.japanese;
// 테이블 헤더 업데이트
const headers = document.querySelectorAll('th');
headers[0].textContent = texts.tableHeaders.title;
headers[1].textContent = texts.tableHeaders.info;
headers[2].textContent = texts.tableHeaders.status;
headers[3].textContent = texts.tableHeaders.role;
headers[4].textContent = texts.tableHeaders.location;
// 로딩 텍스트 업데이트
loading.textContent = texts.loading;
// 필터 텍스트 업데이트
document.getElementById('resetFilters').textContent = texts.filter.reset;
document.querySelector('label[for="korean-support"]').textContent = texts.filter.koreanSupport;
document.querySelector('label[for="korean-not-support"]').textContent =
texts.filter.koreanNotSupport;
}
// 언어 변경 이벤트 리스너
languageRadios.forEach(radio => {
radio.addEventListener('change', e => {
filterState.language = e.target.value;
updateUITexts();
renderGames();
});
});
// 언어 변환 함수
function convertLanguage(language) {
if (!language) return '';
if (filterState.language !== 'ko') return language;
const languages = language.split(',');
const koreanLanguages = [];
for (let i = 0; i < languages.length; i++) {
switch (languages[i].trim()) {
case '日本語':
koreanLanguages.push('일본어');
break;
case '英語':
koreanLanguages.push('영어');
break;
case '韓国語':
koreanLanguages.push('한국어');
break;
case 'フランス語':
koreanLanguages.push('프랑스어');
break;
case 'ドイツ語':
koreanLanguages.push('독일어');
break;
case 'イタリア語':
koreanLanguages.push('이탈리아어');
break;
case 'スペイン語':
koreanLanguages.push('스페인어');
break;
case 'ロシア語':
koreanLanguages.push('러시아어');
break;
case 'オランダ語':
koreanLanguages.push('네덜란드어');
break;
case 'ポルトガル語':
koreanLanguages.push('포르투칼어');
break;
case '中国語 (簡体字)':
koreanLanguages.push('중국어 (간체)');
break;
case '中国語 (繁体字)':
koreanLanguages.push('중국어 (번체)');
break;
default:
koreanLanguages.push(languages[i]);
break;
}
}
return koreanLanguages.join(', ');
}
// 메이커 변환 함수
function convertMaker(maker) {
if (!maker) return '';
if (filterState.language !== 'ko') return maker;
const makers = maker.split(',');
const koreanMakers = [];
for (let i = 0; i < makers.length; i++) {
switch (makers[i].trim()) {
// 메이저 Maker
case '任天堂':
koreanMakers.push('닌텐도');
break;
case 'スクウェア・エニックス':
koreanMakers.push('스퀘어 에닉스');
break;
case 'コーエーテクモゲームス':
koreanMakers.push('코에이 테크모 게임스');
break;
case 'ポケモン':
koreanMakers.push('포켓몬');
break;
case 'バンダイナムコエンターテインメント':
koreanMakers.push('반다이 남코 엔터테인먼트');
break;
case 'アトラス':
koreanMakers.push('아틀러스');
break;
case 'セガ':
koreanMakers.push('세가');
break;
// 소규모 Maker
case '日本一ソフトウェア':
koreanMakers.push('니폰이치 소프트웨어');
break;
case 'エンターグラム':
koreanMakers.push('Entergram');
break;
case 'フライハイワークス':
koreanMakers.push('Flyhigh Works');
break;
case 'フリュー':
koreanMakers.push('후류');
break;
case 'ポノス':
koreanMakers.push('포노스');
break;
case 'マーベラス':
koreanMakers.push('마블러스 엔터테인먼트');
break;
case 'スパイク・チュンソフト':
koreanMakers.push('스파이크 춘 소프트');
break;
case 'ドラガミゲームス':
koreanMakers.push('DRAGAMI GAMES');
break;
case 'アイディアファクトリー':
koreanMakers.push('Idea Factory');
break;
case 'アークシステムワークス':
koreanMakers.push('아크 시스템 웍스');
break;
case 'ユービーアイソフト':
koreanMakers.push('유비소프트');
break;
case 'レイアーク':
koreanMakers.push('레이아크');
break;
case 'ワンオアエイト':
koreanMakers.push('One or Eight');
break;
case 'インティ・クリエイツ':
koreanMakers.push('인티 크리에이츠');
break;
case 'クラウディッドレパードエンタテインメント':
koreanMakers.push('Clouded Leopard Entertainment');
break;
case 'カプコン':
koreanMakers.push('캡콤');
break;
case 'プロトタイプ':
koreanMakers.push('PROTOTYPE Ltd');
break;
case 'ディースリー・パブリッシャー':
koreanMakers.push('D3 퍼블리셔');
break;
case 'タイトー':
koreanMakers.push('타이토');
break;
case 'シーエフケー':
koreanMakers.push('CFK');
break;
case 'アクワイア':
koreanMakers.push('어콰이어');
break;
case '日本ファルコム':
koreanMakers.push('일본 팔콤');
break;
// 거르면 되는 Maker
case 'ヒューネックス':
koreanMakers.push('휴넥스');
break;
case 'アニプレックス':
koreanMakers.push('애니플렉스');
break;
case 'ジー・モード':
koreanMakers.push('G-Mode');
break;
case 'イザナギゲームズ':
koreanMakers.push('IzanagiGames');
break;
case 'ラセングル':
koreanMakers.push('Lasengle');
break;
case 'ケムコ':
koreanMakers.push('켐코');
break;
case 'ネットマーブルコーポレーション':
koreanMakers.push('넷마블');
break;
case '工画堂スタジオ':
koreanMakers.push('코가도 스튜디오');
break;
case '賈船':
koreanMakers.push('COSEN');
break;
case 'ジェムドロップ':
koreanMakers.push('잼드롭');
break;
case 'アレス':
koreanMakers.push('아레스');
break;
case '日本コロムビア':
koreanMakers.push('일본콜롬비아');
break;
case 'クリプトン・フューチャー・メディア':
koreanMakers.push('크립톤 퓨처 미디어');
break;
case 'イマジニア':
koreanMakers.push('이매지니아');
break;
default:
koreanMakers.push(makers[i]);
break;
}
}
return koreanMakers.join(', ');
}
// 태그 변환 함수
function convertTags(tags) {
if (!tags) return '';
if (filterState.language !== 'ko') return tags;
const tagList = tags.split(',');
const koreanTags = [];
for (let i = 0; i < tagList.length; i++) {
switch (tagList[i].trim()) {
case 'パーティー':
koreanTags.push('파티');
break;
case 'シューティング':
koreanTags.push('슈팅');
break;
case '格闘':
koreanTags.push('격투');
break;
case 'パズル':
koreanTags.push('퍼즐');
break;
case 'レース':
koreanTags.push('레이스');
break;
case 'ロールプレイング':
koreanTags.push('롤플레잉');
break;
case 'アクション':
koreanTags.push('액션');
break;
case 'ストラテジー':
koreanTags.push('전략');
break;
case 'ドット絵':
koreanTags.push('도트 그림');
break;
case 'サバイバル':
koreanTags.push('서바이벌');
break;
case 'リズムに合わせて':
koreanTags.push('리듬에 맞춰');
break;
case 'カードゲーム':
koreanTags.push('카드 게임');
break;
case '囲碁・将棋':
koreanTags.push('바둑・쇼기');
break;
case 'ボードゲーム':
koreanTags.push('보드 게임');
break;
case 'サッカー':
koreanTags.push('축구');
break;
case 'テーブルゲーム':
koreanTags.push('테이블 게임');
break;
case 'トランプ':
koreanTags.push('트럼프');
break;
case '麻雀':
koreanTags.push('마작');
break;
case 'トレーニング':
koreanTags.push('트레이닝');
break;
case '視点切りかえ':
koreanTags.push('관점의 전환');
break;
case 'あそびが盛りだくさん':
koreanTags.push('다양한 즐길거리');
break;
case '新しいエリアを切りひらく':
koreanTags.push('새로운 지역을 개척한다');
break;
case 'あそぶたびにマップが新しい':
koreanTags.push('즐길때마다 새로운 맵');
break;
case '大人数でバトル':
koreanTags.push('많은 인원과 배틀');
break;
case '最速タイムにチャレンジ':
koreanTags.push('가장 빠른 시간에 도전');
break;
case 'シミュレーション':
koreanTags.push('시뮬레이션');
break;
case 'スポーツ':
koreanTags.push('스포츠');
break;
case 'バレーボール':
koreanTags.push('배구');
break;
case 'ゴルフ':
koreanTags.push('골프');
break;
case 'バスケットボール':
koreanTags.push('농구');
break;
case 'テニス':
koreanTags.push('테니스');
break;
case '野球':
koreanTags.push('야구');
break;
case 'ボクシング':
koreanTags.push('복싱');
break;
case '経営シミュレーション':
koreanTags.push('경영 시뮬레이션');
break;
case 'コミュニケーション':
koreanTags.push('커뮤니케이션');
break;
case '音楽ゲーム':
koreanTags.push('음악 게임');
break;
case '学習・教育':
koreanTags.push('학습・교육');
break;
case 'ツール':
koreanTags.push('도구');
break;
case 'アドベンチャー':
koreanTags.push('모험');
break;
case '失敗したら最初から':
koreanTags.push('실패하면 처음부터');
break;
case 'なぞ解き':
koreanTags.push('수수께끼');
break;
case 'せまる敵から守りぬく':
koreanTags.push('다가오는 적으로부터 지킨다');
break;
case 'すばやい判断がきめ手':
koreanTags.push('재빠른 판단이 관건');
break;
case 'つくれる・あそべる':
koreanTags.push('만들고 즐긴다');
break;
case 'テキストアドベンチャー':
koreanTags.push('텍스트 어드벤처');
break;
case '恋愛':
koreanTags.push('연애');
break;
case '最高スコアにチャレンジ':
koreanTags.push('최고 점수에 도전');
break;
case '世界を自由にかけ回る':
koreanTags.push('세계를 자유롭게 돌아다니다');
break;
case '落下に注意':
koreanTags.push('낙하주의');
break;
case '目的はあなた次第':
koreanTags.push('목적은 당신이 선택');
break;
case '戦うたびに強くなる':
koreanTags.push('싸울수록 성장');
break;
case '手足や体と連動して操作':
koreanTags.push('몸과 연동하여 조작');
break;
case 'オンラインで対戦':
koreanTags.push('온라인 대전');
break;
case 'オンラインで協力':
koreanTags.push('온라인 협력');
break;
case 'キャラクターカスタマイズ':
koreanTags.push('캐릭터 커스터마이즈');
break;
case '3人称視点':
koreanTags.push('3인칭 시점');
break;
case 'Toy-Conが使える':
koreanTags.push('Toy-Con을 사용할 수 있다');
break;
case '難易度が選べる':
koreanTags.push('난이도 선택 가능');
break;
case 'キャラクターボイス':
koreanTags.push('케릭터 음성');
break;
case 'オンラインでフレンドと':
koreanTags.push('친구와 온라인으로');
break;
case '1台の本体でいっしょにあそべる':
koreanTags.push('한 대의 본체에서 함께 놀 수 있다');
break;
case 'ともだちや家族と集まって':
koreanTags.push('친구와 가족과 함께');
break;
case 'オンラインランキング':
koreanTags.push('온라인 랭킹');
break;
case '本体を持ちよってあそべる':
koreanTags.push('게임기를 들고 플레이');
break;
case 'クロスプラットフォームプレイ対応':
koreanTags.push('크로스 플랫폼 플레이 대응');
break;
default:
koreanTags.push(tagList[i]);
break;
}
}
return koreanTags.join(', ');
}
// 출시일 변환 함수
function convertReleaseDate(releaseDate) {
if (!releaseDate) return '';
if (filterState.language !== 'ko') return releaseDate;
const date = new Date(releaseDate.replace(/年|月/g, '/').replace('日', ''));
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
return `${year}${month}${day}`;
}
// 국가 변환 함수
function convertCountry(country) {
if (!country) return '';
switch (country) {
case 'JPN':
return filterState.language === 'ko' ? '일본판' : '日本版';
case 'KOR':
return filterState.language === 'ko' ? '한국판' : '韓国版';
default:
return country;
}
}
function formatGameData(game) {
return {
...game,
formattedTitle: filterState.language === 'ko' ? game.koTitle || game.title : game.title,
formattedReleaseDate: convertReleaseDate(game.release),
formattedMaker: convertMaker(game.maker),
formattedLanguage: convertLanguage(game.language),
formattedTags: convertTags(game.tags),
formattedCountry: convertCountry(game.country),
};
}
function getStatusClass(status) {
switch (status) {
case 'package':
return 'bg-green-100 text-green-800';
case 'download':
return 'bg-yellow-100 text-yellow-800';
case 'expansion':
return 'bg-blue-100 text-blue-800';
default:
return '';
}
}
function getCountryClass(country) {
return country === 'JPN'
? 'text-red-600 hover:text-red-900'
: 'text-indigo-600 hover:text-indigo-900';
}
function createGameRow(game, index) {
const tr = document.createElement('tr');
tr.className = `cursor-pointer hover:bg-indigo-50 ${index % 2 === 0 ? '' : 'bg-gray-50'}`;
// 클릭 시 nsw-detail.html로 이동 (no 쿼리 포함)
tr.addEventListener('click', () => {
window.location.href = `nsw-detail.html?no=${encodeURIComponent(game.no)}`;
});
tr.innerHTML = `
<td class="py-4 pl-4 pr-3 text-sm">
<div class="flex items-center">
<div class="h-10 w-10 flex-shrink-0">
<img class="h-10 w-10 rounded-xl object-cover" src="${game.thumbnail}" alt="" />
</div>
<div class="ml-4">
<div class="font-medium text-gray-900 ${
game.status === 'sold' ? 'text-red-600 line-through' : ''
}">
${game.no}. ${game.formattedTitle}
</div>
<div class="text-gray-500">${game.formattedReleaseDate}</div>
</div>
</div>
</td>
<td class="hidden w-3/12 px-3 py-4 text-sm text-gray-500 lg:table-cell">
<div class="text-gray-900">${game.formattedMaker}</div>
<div class="text-gray-500">${game.formattedLanguage}</div>
</td>
<td class="px-3 py-4 text-sm text-gray-500 mx-auto">
<div class="flex justify-center rounded-full px-2 text-xs font-semibold leading-5 ${getStatusClass(
game.status,
)}">
${game.status}
</div>
<div class="text-xs mt-1 text-center text-gray-300 hidden">
${game?.purchaseInformation?.date || ''}
</div>
</td>
<td class="w-3/12 hidden px-3 py-4 text-sm text-gray-500 lg:table-cell">
${game.formattedTags}
</td>
<td class="hidden py-4 pl-3 pr-4 text-right text-sm font-medium sm:table-cell">
<div class="${getCountryClass(game.country)}">
${game.formattedCountry}
</div>
</td>
`;
return tr;
}
// 필터 초기화 함수
function resetFilters() {
// 모든 체크박스 해제
document.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.checked = false;
});
// 필터 상태 초기화
filterState.filters = {
language: [],
status: [],
country: [],
cero: [],
};
// 게임 목록 다시 렌더링
renderGames();
}
// 필터 초기화 버튼 이벤트 리스너 설정
document.getElementById('resetFilters').addEventListener('click', resetFilters);
// 필터 체크박스 이벤트 리스너 설정
function setupFilterListeners() {
// 언어 필터
document.querySelectorAll('input[name="language-filter"]').forEach(checkbox => {
checkbox.addEventListener('change', () => {
updateFilters('language');
renderGames();
});
});
// 상태 필터
document.querySelectorAll('input[name="status-filter"]').forEach(checkbox => {
checkbox.addEventListener('change', () => {
updateFilters('status');
renderGames();
});
});
// 국가 필터
document.querySelectorAll('input[name="country-filter"]').forEach(checkbox => {
checkbox.addEventListener('change', () => {
updateFilters('country');
renderGames();
});
});
// CERO 필터
document.querySelectorAll('input[name="cero-filter"]').forEach(checkbox => {
checkbox.addEventListener('change', () => {
updateFilters('cero');
renderGames();
});
});
}
// 필터 상태 업데이트
function updateFilters(type) {
const checkboxes = document.querySelectorAll(`input[name="${type}-filter"]:checked`);
filterState.filters[type] = Array.from(checkboxes).map(checkbox => checkbox.value);
}
// 필터링 함수
function filterGames(games) {
return games.filter(game => {
// 언어 필터
if (filterState.filters.language.length > 0) {
const hasKorean = game.language.includes('韓国語');
const hasKoreanFilter = filterState.filters.language.includes('koreanSupport');
const hasNotSupportedFilter = filterState.filters.language.includes('koreanNotSupport');
// 한국어 지원과 미지원이 모두 체크된 경우 모든 게임 표시
if (hasKoreanFilter && hasNotSupportedFilter) {
return true;
}
// 한국어 지원만 체크된 경우 한국어 지원 게임만 표시
if (hasKoreanFilter && !hasNotSupportedFilter) {
return hasKorean;
}
// 한국어 미지원만 체크된 경우 한국어 미지원 게임만 표시
if (!hasKoreanFilter && hasNotSupportedFilter) {
return !hasKorean;
}
// 아무것도 체크되지 않은 경우 필터링하지 않음
return true;
}
// 상태 필터
if (filterState.filters.status.length > 0) {
const hasExtensionFilter = filterState.filters.status.includes('extension');
const hasOtherStatusFilters =
filterState.filters.status.filter(status => status !== 'extension').length > 0;
// extension 필터가 체크된 경우
if (hasExtensionFilter) {
// extension 값이 null이 아닌 게임만 표시
if (game.extension === null) return false;
}
// 다른 상태 필터가 체크된 경우
if (hasOtherStatusFilters) {
const otherStatuses = filterState.filters.status.filter(status => status !== 'extension');
if (!otherStatuses.includes(game.status)) return false;
}
}
// 국가 필터
if (
filterState.filters.country.length > 0 &&
!filterState.filters.country.includes(game.country)
) {
return false;
}
// CERO 필터
if (filterState.filters.cero.length > 0 && !filterState.filters.cero.includes(game.cero)) {
return false;
}
return true;
});
}
// 정렬 함수
function sortGames(games) {
switch (filterState.sortBy) {
case 'sortByNoDesc':
return [...games].sort((a, b) => b.no - a.no);
case 'sortByNo':
return [...games].sort((a, b) => a.no - b.no);
case 'sortByDateDesc':
return [...games].sort((a, b) => {
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
const bDate = new Date(b.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
const dateDiff = new Date(bDate) - new Date(aDate);
return dateDiff === 0 ? b.no - a.no : dateDiff;
});
case 'sortByDate':
return [...games].sort((a, b) => {
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
const bDate = new Date(b.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
const dateDiff = new Date(aDate) - new Date(bDate);
return dateDiff === 0 ? b.no - a.no : dateDiff;
});
case 'sortByPurchaseDateDesc':
return [...games].sort((a, b) => {
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
return !a.purchaseInformation?.date ? 1 : -1;
}
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
const dateDiff = new Date(bDate) - new Date(aDate);
return dateDiff === 0 ? b.no - a.no : dateDiff;
});
case 'sortByPurchaseDate':
return [...games].sort((a, b) => {
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
return !a.purchaseInformation?.date ? 1 : -1;
}
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
const dateDiff = new Date(aDate) - new Date(bDate);
return dateDiff === 0 ? b.no - a.no : dateDiff;
});
case 'sortByRandom':
return [...games].sort(() => Math.random() - 0.5);
default:
return games;
}
}
// 정렬 UI 초기화
function setupSortUI() {
const sortButton = document.getElementById('sort-button');
const sortOptions = document.getElementById('sort-options');
const sortLabel = document.getElementById('sort-label');
// 정렬 버튼 클릭 이벤트
sortButton.addEventListener('click', () => {
sortOptions.classList.toggle('hidden');
const isExpanded = sortOptions.classList.contains('hidden') ? 'false' : 'true';
sortButton.setAttribute('aria-expanded', isExpanded);
});
// 정렬 옵션 클릭 이벤트
sortOptions.querySelectorAll('li').forEach(option => {
option.addEventListener('click', () => {
const value = option.getAttribute('data-value');
const name = uiTexts[filterState.language].sortOptions[value];
// 선택된 옵션 업데이트
sortOptions.querySelectorAll('li').forEach(li => {
li.querySelector('span').classList.remove('font-semibold');
li.querySelector('svg')?.parentElement?.classList.add('hidden');
});
option.querySelector('span').classList.add('font-semibold');
const checkIcon = option.querySelector('svg')?.parentElement;
if (checkIcon) checkIcon.classList.remove('hidden');
// 정렬 상태 업데이트
filterState.sortBy = value;
sortButton.querySelector('span').textContent = name;
sortOptions.classList.add('hidden');
sortButton.setAttribute('aria-expanded', 'false');
// 게임 목록 다시 렌더링
renderGames();
});
});
// 외부 클릭 시 드롭다운 닫기
document.addEventListener('click', e => {
if (!sortButton.contains(e.target) && !sortOptions.contains(e.target)) {
sortOptions.classList.add('hidden');
sortButton.setAttribute('aria-expanded', 'false');
}
});
// 정렬 옵션 텍스트 업데이트
function updateSortOptionsText() {
const texts = uiTexts[filterState.language].sortOptions;
sortLabel.textContent = filterState.language === 'ko' ? '정렬' : '並び替え';
sortButton.querySelector('span').textContent = texts[filterState.sortBy];
sortOptions.querySelectorAll('li').forEach(option => {
const value = option.getAttribute('data-value');
option.querySelector('span').textContent = texts[value];
});
}
// 언어 변경 시 정렬 옵션 텍스트 업데이트
languageRadios.forEach(radio => {
radio.addEventListener('change', e => {
filterState.language = e.target.value;
updateSortOptionsText();
});
});
// 초기 정렬 옵션 텍스트 설정
updateSortOptionsText();
}
// 게임 렌더링 함수 수정
function renderGames() {
loading.classList.remove('hidden');
// 데이터 포맷팅
const formattedGames = NSW_DB.map(formatGameData);
// 필터링 적용
const filteredGames = filterGames(formattedGames);
// 정렬 적용
const sortedGames = sortGames(filteredGames);
// 게임 개수 업데이트
gameCount.textContent = `${sortedGames.length} ${uiTexts[filterState.language].count}`;
// 테이블 내용 업데이트
gameList.innerHTML = '';
sortedGames.forEach((game, index) => {
gameList.appendChild(createGameRow(game, index));
});
loading.classList.add('hidden');
}
// 초기화
setupFilterListeners();
setupSortUI();
updateUITexts();
renderGames();
});