Compare commits
14 Commits
main
...
feature/us
| Author | SHA1 | Date | |
|---|---|---|---|
| 11ec940e48 | |||
| 707da3b402 | |||
| a69b655995 | |||
| e0318cbf91 | |||
| 2b93c8db72 | |||
| 908fd1035c | |||
| 37926e9d7f | |||
| 9447623ece | |||
| 44426a633d | |||
| b15ef36f62 | |||
| 8bdbe73900 | |||
| d63e3db986 | |||
| fd8c52dbce | |||
| 7904b6b89c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
.vscode/
|
||||||
|
|||||||
3555
db/amiibo.db.js
3555
db/amiibo.db.js
File diff suppressed because it is too large
Load Diff
8041
db/nsw-sale.db.js
8041
db/nsw-sale.db.js
File diff suppressed because it is too large
Load Diff
9341
db/nsw.db.js
9341
db/nsw.db.js
File diff suppressed because it is too large
Load Diff
7403
db/nsw.resale.db.js
Normal file
7403
db/nsw.resale.db.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,9 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-05-19
|
||||||
|
- 임시 추정가와 실제 확인가를 구분하기 위해 `sale.priceVerified` 불리언을 도입하고, 확인 완료 항목은 제목 앞 노란 별로 표시하도록 했다.
|
||||||
|
- 패키지 개봉 여부는 판매 상태(`status`)와 분리해 `itemCondition`(SEALED/OPENED)으로 관리하고 목록·상세에 함께 노출하도록 했다.
|
||||||
|
|
||||||
## 2026-03-30
|
## 2026-03-30
|
||||||
- 게임 목록 서비스의 기준 화면을 `index.html` + `nsw-detail.html` 2개로 단순화했다.
|
- 게임 목록 서비스의 기준 화면을 `index.html` + `nsw-detail.html` 2개로 단순화했다.
|
||||||
- 데이터 저장소를 별도 API 없이 정적 파일(`db/nsw.db.js`)로 유지하여 배포 복잡도를 낮췄다.
|
- 데이터 저장소를 별도 API 없이 정적 파일(`db/nsw.db.js`)로 유지하여 배포 복잡도를 낮췄다.
|
||||||
|
|||||||
13
docs/map.md
13
docs/map.md
@@ -3,30 +3,31 @@
|
|||||||
## 메인 목록 화면 (`/index.html`)
|
## 메인 목록 화면 (`/index.html`)
|
||||||
- 담당 파일: `index.html`
|
- 담당 파일: `index.html`
|
||||||
- 연결 스크립트: `script/nsw.js`
|
- 연결 스크립트: `script/nsw.js`
|
||||||
- 데이터 소스: `db/nsw.db.js`
|
- 데이터 소스: `db/nsw.resale.db.js`
|
||||||
- 주요 UI:
|
- 주요 UI:
|
||||||
- 언어 전환(한국어/일본어)
|
- 언어 전환(한국어/일본어)
|
||||||
- 검색 입력
|
- 검색 입력
|
||||||
- 필터(언어 지원, 상태, 에디션, CERO)
|
- 필터(언어 지원, 판매 상태, 에디션, CERO)
|
||||||
- 정렬 드롭다운
|
- 정렬 드롭다운
|
||||||
- 게임 목록 테이블
|
- 게임 목록 테이블(가격 확인 별, 판매가, 판매 상태, 제품 상태, 지역)
|
||||||
- 사용자 동작:
|
- 사용자 동작:
|
||||||
- 목록 행 클릭 시 `nsw-detail.html?no={게임번호}`로 이동
|
- 목록 행 클릭 시 `nsw-detail.html?no={게임번호}`로 이동
|
||||||
|
|
||||||
## 상세 화면 (`/nsw-detail.html`)
|
## 상세 화면 (`/nsw-detail.html`)
|
||||||
- 담당 파일: `nsw-detail.html`
|
- 담당 파일: `nsw-detail.html`
|
||||||
- 연결 스크립트: `script/nsw-detail.js`
|
- 연결 스크립트: `script/nsw-detail.js`
|
||||||
- 데이터 소스: `db/nsw.db.js`
|
- 데이터 소스: `db/nsw.resale.db.js`
|
||||||
- 주요 UI:
|
- 주요 UI:
|
||||||
- 상단 대표 이미지
|
- 상단 대표 이미지
|
||||||
- 게임 기본 정보(용량, 플레이 모드, 메이커, 언어, 등급, 출시일)
|
- 게임 기본 정보(용량, 플레이 모드, 메이커, 언어, 등급, 출시일)
|
||||||
- 구매 정보(구매일, 구매처, 가격, 주문번호, 추가 콘텐츠)
|
- 판매 정보(판매가, 가격 기준, 제품 상태, 가격 확인 여부, 기준일)
|
||||||
- 사용자 동작:
|
- 사용자 동작:
|
||||||
- URL의 `no` 값으로 게임을 조회
|
- URL의 `no` 값으로 게임을 조회
|
||||||
- 해당 번호가 없으면 알림 후 목록 페이지로 리다이렉트
|
- 해당 번호가 없으면 알림 후 목록 페이지로 리다이렉트
|
||||||
|
|
||||||
## 데이터 전용 파일
|
## 데이터 전용 파일
|
||||||
- `db/nsw.db.js`: 닌텐도 스위치 게임 데이터 본문
|
- `db/nsw.resale.db.js`: 닌텐도 스위치 중고 판매 목록·가격 데이터
|
||||||
|
- `db/nsw.db.js`: 레거시 보유 목록 데이터
|
||||||
- `db/nsw-sale.db.js`: 세일 관련 데이터(현재 화면 연결 없음)
|
- `db/nsw-sale.db.js`: 세일 관련 데이터(현재 화면 연결 없음)
|
||||||
- `db/amiibo.db.js`: 아미보 관련 데이터(현재 화면 연결 없음)
|
- `db/amiibo.db.js`: 아미보 관련 데이터(현재 화면 연결 없음)
|
||||||
|
|
||||||
|
|||||||
26
docs/spec.md
26
docs/spec.md
@@ -7,18 +7,18 @@
|
|||||||
|
|
||||||
## 화면/라우팅
|
## 화면/라우팅
|
||||||
- 목록 화면: `index.html`
|
- 목록 화면: `index.html`
|
||||||
- 주요 기능: 검색, 필터(언어/상태/국가/CERO), 정렬, 게임 개수 표시
|
- 주요 기능: 검색, 필터(언어/판매상태/국가/CERO), 정렬, 게임 개수 표시, 판매 정보 배지 표시
|
||||||
- 데이터 소스: `db/nsw.db.js`
|
- 데이터 소스: `db/nsw.resale.db.js` (`export default` → `items` 배열)
|
||||||
- 스크립트: `script/nsw.js`
|
- 스크립트: `script/nsw.js`
|
||||||
- 상세 화면: `nsw-detail.html`
|
- 상세 화면: `nsw-detail.html`
|
||||||
- 주요 기능: 게임 기본 정보/구매 정보 상세 표시
|
- 주요 기능: 게임 기본 정보/판매 정보 상세 표시
|
||||||
- 진입 방식: `nsw-detail.html?no={gameNo}`
|
- 진입 방식: `nsw-detail.html?no={gameNo}`
|
||||||
- 데이터 소스: `db/nsw.db.js`
|
- 데이터 소스: `db/nsw.resale.db.js`
|
||||||
- 스크립트: `script/nsw-detail.js`
|
- 스크립트: `script/nsw-detail.js`
|
||||||
|
|
||||||
## 데이터 구조
|
## 데이터 구조 (중고 판매 DB)
|
||||||
- 파일: `db/nsw.db.js`
|
- 파일: `db/nsw.resale.db.js`
|
||||||
- 형태: `export default []` 배열
|
- 형태: `export const NSW_RESALE_DB = { metadata, items }`, `export default items`
|
||||||
- 주요 필드:
|
- 주요 필드:
|
||||||
- `no` (number): 게임 고유 순번
|
- `no` (number): 게임 고유 순번
|
||||||
- `title` (string): 일본어/원문 타이틀
|
- `title` (string): 일본어/원문 타이틀
|
||||||
@@ -38,15 +38,13 @@
|
|||||||
- `release` (string|null): 출시일(예: `2017年7月20日`)
|
- `release` (string|null): 출시일(예: `2017年7月20日`)
|
||||||
- `tags` (string|null): 장르/특성 태그
|
- `tags` (string|null): 장르/특성 태그
|
||||||
- `extension` (string[]|null): 추가 콘텐츠 목록
|
- `extension` (string[]|null): 추가 콘텐츠 목록
|
||||||
- `status` (string): `package` | `download` | `expansion` | `sold`
|
- `status` (string): `available` | `sold` (판매 가능/판매완료)
|
||||||
|
- `itemCondition` (string|null): 판매자가 직접 작성하는 제품 상태. 예: `미개봉 새제품`, `초회 한정판`
|
||||||
- `country` (string): `KOR` | `JPN`
|
- `country` (string): `KOR` | `JPN`
|
||||||
- `cero` (string|null): CERO 등급
|
- `cero` (string|null): CERO 등급
|
||||||
- `purchaseInformation` (object|null):
|
- `sale` (object):
|
||||||
- `date` (string|null)
|
- `currency` (string): `KRW`
|
||||||
- `store` (string|null)
|
- `suggestedPrice` (`number` | `"none"`): 수기 입력된 판매가(KRW). 미입력 항목은 `"none"`.
|
||||||
- `purchase` (number|null)
|
|
||||||
- `monetary` (string|null)
|
|
||||||
- `orderNumber` (string|null)
|
|
||||||
|
|
||||||
## 필터/정렬 동작
|
## 필터/정렬 동작
|
||||||
- 필터:
|
- 필터:
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
- 레거시 페이지/리소스(아미보, 세일 관련) 제거 후 문서와 실제 구조 정합성 점검이 필요하다.
|
- 레거시 페이지/리소스(아미보, 세일 관련) 제거 후 문서와 실제 구조 정합성 점검이 필요하다.
|
||||||
|
|
||||||
## 다음 작업
|
## 다음 작업
|
||||||
|
- `sale.suggestedPrice`를 타이틀별로 수기 확인해 숫자 가격으로 순차 입력한다.
|
||||||
|
- `itemCondition` 값을 타이틀별로 실제 데이터에 맞게 채운다.
|
||||||
- 목록/상세 페이지의 경로 및 텍스트 다국어 키를 재검증한다.
|
- 목록/상세 페이지의 경로 및 텍스트 다국어 키를 재검증한다.
|
||||||
- 필터 조합(언어+상태+국가+CERO) 테스트 케이스를 작성한다.
|
- 필터 조합(언어+상태+국가+CERO) 테스트 케이스를 작성한다.
|
||||||
- 데이터 스키마 검증 스크립트를 추가해 누락 필드와 오탈자를 자동 확인한다.
|
- 데이터 스키마 검증 스크립트를 추가해 누락 필드와 오탈자를 자동 확인한다.
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
# 작업 이력
|
# 작업 이력
|
||||||
|
|
||||||
|
## v2026.05.21-01
|
||||||
|
- `db/nsw.resale.db.js` 기존 추정 판매가·가격 근거 필드를 제거하고 `sale.suggestedPrice: "none"` 상태로 초기화
|
||||||
|
- `script/nsw.js`, `script/nsw-detail.js` 가격 미입력 항목에 “가격은 순차적으로 작성중입니다.” 안내 표시
|
||||||
|
- 가격 정렬 시 숫자 가격이 있는 항목만 우선 정렬하고 미입력 항목은 뒤로 배치
|
||||||
|
|
||||||
|
## v2026.05.19-02
|
||||||
|
- `script/nsw.js`, `script/nsw-detail.js` 가격 확인 별(⭐)과 순번 사이 공백 추가
|
||||||
|
|
||||||
|
## v2026.05.19-01
|
||||||
|
- `db/nsw.resale.db.js` 가격 확인 플래그 `sale.priceVerified` 추가
|
||||||
|
- `db/nsw.resale.db.js` 제품 상태 `itemCondition`(SEALED/OPENED) enum·필드 추가
|
||||||
|
- `script/nsw.js` 가격 확인 타이틀 노란 별 표시, 목록 제품 상태 컬럼 반영
|
||||||
|
- `script/nsw-detail.js` 상세 화면 별 표시·제품 상태·가격 확인 문구 반영
|
||||||
|
- `index.html` 테이블 제품 상태 열 헤더 반영
|
||||||
|
|
||||||
## v2026.03.30-04
|
## v2026.03.30-04
|
||||||
- 커밋 제목 규칙 추가: `vYYYY.MM.DD-번호 내용`
|
- 커밋 제목 규칙 추가: `vYYYY.MM.DD-번호 내용`
|
||||||
- `docs/convention.md` 커밋 제목 형식 가이드 반영
|
- `docs/convention.md` 커밋 제목 형식 가이드 반영
|
||||||
|
|||||||
164
index.html
164
index.html
@@ -15,6 +15,46 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section aria-labelledby="products-heading" class="pt-6 pb-12">
|
<section aria-labelledby="products-heading" class="pt-6 pb-12">
|
||||||
|
<div id="mobileSearchForm" class="mb-4 lg:hidden">
|
||||||
|
<label
|
||||||
|
for="mobile-search-input"
|
||||||
|
class="block text-sm font-medium leading-6 text-gray-900">
|
||||||
|
직접 검색
|
||||||
|
</label>
|
||||||
|
<div class="relative mt-2 flex rounded-md shadow-sm">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 px-3 text-gray-500 sm:text-sm">
|
||||||
|
게임명
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="mobile-search-input"
|
||||||
|
data-search-input
|
||||||
|
enterkeyhint="search"
|
||||||
|
autocomplete="off"
|
||||||
|
oninput="window.updateNswSearch && window.updateNswSearch(this.value)"
|
||||||
|
onchange="window.updateNswSearch && window.updateNswSearch(this.value)"
|
||||||
|
class="block w-full min-w-0 flex-1 rounded-none rounded-r-md px-2 py-2 pr-9 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
|
placeholder=" " />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="absolute inset-y-0 right-0 z-10 flex items-center pr-3"
|
||||||
|
id="mobile-reset-search"
|
||||||
|
aria-label="검색어 지우기"
|
||||||
|
onclick="window.clearNswSearch && window.clearNswSearch()">
|
||||||
|
<svg
|
||||||
|
class="h-5 w-5 text-gray-400"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="currentColor">
|
||||||
|
<path
|
||||||
|
fill-rule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
|
||||||
|
clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-x-8 gap-y-2 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-x-8 gap-y-2 lg:grid-cols-4">
|
||||||
<!-- 필터 섹션 -->
|
<!-- 필터 섹션 -->
|
||||||
<div class="lg:block">
|
<div class="lg:block">
|
||||||
@@ -78,7 +118,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<form id="searchForm" class="mb-4">
|
<form id="searchForm" class="mb-4" onsubmit="return false">
|
||||||
<label
|
<label
|
||||||
for="search-input"
|
for="search-input"
|
||||||
class="block text-sm font-medium leading-6 text-gray-900">
|
class="block text-sm font-medium leading-6 text-gray-900">
|
||||||
@@ -91,13 +131,18 @@
|
|||||||
</span>
|
</span>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="search-input"
|
|
||||||
id="search-input"
|
id="search-input"
|
||||||
class="block w-full px-2 min-w-0 flex-1 rounded-none rounded-r-md py-1.5 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
data-search-input
|
||||||
|
enterkeyhint="search"
|
||||||
|
autocomplete="off"
|
||||||
|
oninput="window.updateNswSearch && window.updateNswSearch(this.value)"
|
||||||
|
onchange="window.updateNswSearch && window.updateNswSearch(this.value)"
|
||||||
|
class="block w-full px-2 min-w-0 flex-1 rounded-none rounded-r-md py-1.5 pr-9 text-gray-900 ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
||||||
placeholder=" " />
|
placeholder=" " />
|
||||||
<div
|
<div
|
||||||
class="cursor-pointer absolute inset-y-0 right-0 flex items-center pr-3"
|
class="cursor-pointer absolute inset-y-0 right-0 z-10 flex items-center pr-3"
|
||||||
id="reset-search">
|
id="reset-search"
|
||||||
|
onclick="window.clearNswSearch && window.clearNswSearch()">
|
||||||
<svg
|
<svg
|
||||||
class="h-5 w-5 text-gray-400"
|
class="h-5 w-5 text-gray-400"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -158,39 +203,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 상태 필터 -->
|
<!-- 판매 상태 필터 -->
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<h3 class="text-base font-medium text-gray-900">Status</h3>
|
<h3 class="text-base font-medium text-gray-900">Sale Status</h3>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input
|
<input
|
||||||
id="package"
|
id="available"
|
||||||
name="status-filter"
|
name="status-filter"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="package"
|
value="available"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
||||||
<label for="package" class="ml-2 text-sm text-gray-900">Package</label>
|
<label for="available" class="ml-2 text-sm text-gray-900">
|
||||||
</div>
|
판매 가능
|
||||||
<div class="flex items-center">
|
|
||||||
<input
|
|
||||||
id="download"
|
|
||||||
name="status-filter"
|
|
||||||
type="checkbox"
|
|
||||||
value="download"
|
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
|
||||||
<label for="download" class="ml-2 text-sm text-gray-900">
|
|
||||||
Download
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<input
|
|
||||||
id="extension"
|
|
||||||
name="status-filter"
|
|
||||||
type="checkbox"
|
|
||||||
value="extension"
|
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
|
||||||
<label for="extension" class="ml-2 text-sm text-gray-900">
|
|
||||||
Extension
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -200,7 +225,7 @@
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
value="sold"
|
value="sold"
|
||||||
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
class="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600" />
|
||||||
<label for="sold" class="ml-2 text-sm text-gray-900">Sold</label>
|
<label for="sold" class="ml-2 text-sm text-gray-900">판매완료</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -283,11 +308,14 @@
|
|||||||
<div class="lg:col-span-3">
|
<div class="lg:col-span-3">
|
||||||
<div class="sm:px-6 lg:px-8">
|
<div class="sm:px-6 lg:px-8">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<div class="flex items-center justify-between sm:flex-auto sm:-mx-6 lg:-mx-4">
|
<div class="flex flex-wrap items-center justify-between gap-3 sm:flex-auto sm:-mx-6 lg:-mx-4">
|
||||||
<p class="mt-2 sm:mt-0 text-sm text-gray-700">
|
<p class="mt-2 sm:mt-0 text-sm text-gray-700">
|
||||||
개수:
|
개수:
|
||||||
<span id="gameCount">0</span>
|
<span id="gameCount">0</span>
|
||||||
</p>
|
</p>
|
||||||
|
<p id="priceProgressNotice" class="mt-2 max-w-md text-sm text-gray-500 sm:mt-0">
|
||||||
|
우선 이런 게임들이 있습니다. 가격은 순차적으로 작성중입니다.
|
||||||
|
</p>
|
||||||
<div class="sort">
|
<div class="sort">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<label id="sort-label" class="block text-sm/6 font-medium text-gray-900">
|
<label id="sort-label" class="block text-sm/6 font-medium text-gray-900">
|
||||||
@@ -300,7 +328,7 @@
|
|||||||
id="sort-button"
|
id="sort-button"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-haspopup="true">
|
aria-haspopup="true">
|
||||||
<span class="col-start-1 row-start-1 truncate pr-6">순번 최신순</span>
|
<span class="col-start-1 row-start-1 truncate pr-6">무작위</span>
|
||||||
<svg
|
<svg
|
||||||
class="w-4 h-4 transform transition-transform duration-200"
|
class="w-4 h-4 transform transition-transform duration-200"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -320,46 +348,6 @@
|
|||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
role="listbox"
|
role="listbox"
|
||||||
aria-labelledby="sort-label">
|
aria-labelledby="sort-label">
|
||||||
<li
|
|
||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
|
||||||
role="option"
|
|
||||||
data-value="sortByNoDesc">
|
|
||||||
<span class="block truncate font-semibold">순번 최신순</span>
|
|
||||||
<span
|
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
|
|
||||||
<svg
|
|
||||||
class="size-5"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
|
||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
|
||||||
role="option"
|
|
||||||
data-value="sortByNo">
|
|
||||||
<span class="block truncate">순번 과거순</span>
|
|
||||||
<span
|
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
|
||||||
<svg
|
|
||||||
class="size-5"
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="currentColor"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-slot="icon">
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
<li
|
<li
|
||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
||||||
role="option"
|
role="option"
|
||||||
@@ -403,8 +391,8 @@
|
|||||||
<li
|
<li
|
||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
||||||
role="option"
|
role="option"
|
||||||
data-value="sortByPurchaseDateDesc">
|
data-value="sortByPriceDesc">
|
||||||
<span class="block truncate">구매일 최신순</span>
|
<span class="block truncate">판매가 높은순</span>
|
||||||
<span
|
<span
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
||||||
<svg
|
<svg
|
||||||
@@ -423,8 +411,8 @@
|
|||||||
<li
|
<li
|
||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
||||||
role="option"
|
role="option"
|
||||||
data-value="sortByPurchaseDate">
|
data-value="sortByPrice">
|
||||||
<span class="block truncate">구매일 과거순</span>
|
<span class="block truncate">판매가 낮은순</span>
|
||||||
<span
|
<span
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
||||||
<svg
|
<svg
|
||||||
@@ -444,9 +432,9 @@
|
|||||||
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
class="relative cursor-pointer hover:bg-gray-100 select-none py-2 pl-3 pr-9 text-gray-900"
|
||||||
role="option"
|
role="option"
|
||||||
data-value="sortByRandom">
|
data-value="sortByRandom">
|
||||||
<span class="block truncate">무작위</span>
|
<span class="block truncate font-semibold">무작위</span>
|
||||||
<span
|
<span
|
||||||
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 hidden">
|
class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
|
||||||
<svg
|
<svg
|
||||||
class="size-5"
|
class="size-5"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
@@ -480,24 +468,14 @@
|
|||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">
|
class="hidden w-32 px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell">
|
||||||
Infomation
|
Infomation
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
scope="col"
|
scope="col"
|
||||||
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">
|
class="hidden w-28 px-3 py-3.5 text-left text-sm font-semibold text-gray-900 sm:table-cell">
|
||||||
Status
|
Status
|
||||||
</th>
|
</th>
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="hidden px-3 py-3.5 text-left text-sm font-semibold text-gray-900 lg:table-cell">
|
|
||||||
Role
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
scope="col"
|
|
||||||
class="hidden py-3.5 pl-3 pr-4 text-left text-sm font-semibold text-gray-900 sm:table-cell">
|
|
||||||
Location
|
|
||||||
</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody
|
<tbody
|
||||||
@@ -517,6 +495,6 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="./script/nsw.js"></script>
|
<script type="module" src="./script/nsw.js?v=20260521-badges-v2"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
<div class="flex justify-between px-4 py-5 sm:px-6">
|
<div class="flex justify-between px-4 py-5 sm:px-6">
|
||||||
<div>
|
<div>
|
||||||
<h3 id="purchaseTitle" class="text-base font-semibold leading-6 text-gray-900">
|
<h3 id="purchaseTitle" class="text-base font-semibold leading-6 text-gray-900">
|
||||||
구매 정보
|
판매 정보
|
||||||
</h3>
|
</h3>
|
||||||
<p id="purchaseGameTitle" class="mt-1 max-w-2xl text-sm text-gray-500"></p>
|
<p id="purchaseGameTitle" class="mt-1 max-w-2xl text-sm text-gray-500"></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="purchaseInfo" class="border-t border-gray-200 px-4 py-5 sm:px-6">
|
<div id="purchaseInfo" class="border-t border-gray-200 px-4 py-5 sm:px-6">
|
||||||
<!-- 구매 정보가 여기에 동적으로 추가됩니다 -->
|
<!-- 판매 정보가 여기에 동적으로 추가됩니다 -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import NSW_DB from '../db/nsw.db.js';
|
import NSW_DB from '../db/nsw.resale.db.js';
|
||||||
|
|
||||||
|
const SHOW_RECOMMENDED_PRICE_RANGE_BY_DEFAULT = false;
|
||||||
|
|
||||||
|
/** @type {string} 가격 확인 완료 표시 */
|
||||||
|
const VERIFIED_PRICE_MARK = '⭐';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// URL에서 게임 번호 가져오기
|
// URL에서 게임 번호 가져오기
|
||||||
@@ -18,7 +23,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const texts = {
|
const texts = {
|
||||||
ko: {
|
ko: {
|
||||||
infoTitle: '게임 정보',
|
infoTitle: '게임 정보',
|
||||||
purchaseTitle: '구매 정보',
|
purchaseTitle: '판매 정보',
|
||||||
requiredCapacity: '필요한 용량',
|
requiredCapacity: '필요한 용량',
|
||||||
playMode: '플레이 모드',
|
playMode: '플레이 모드',
|
||||||
playUser: '플레이 인원',
|
playUser: '플레이 인원',
|
||||||
@@ -31,10 +36,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cero: '심의 등급',
|
cero: '심의 등급',
|
||||||
iarc: '심의 등급',
|
iarc: '심의 등급',
|
||||||
releaseDate: '출시일',
|
releaseDate: '출시일',
|
||||||
purchaseDate: '구매일',
|
suggestedPrice: '판매가',
|
||||||
store: '구매처',
|
pricePending: '-',
|
||||||
price: '구매 가격',
|
priceRange: '판매가 범위',
|
||||||
orderNumber: '주문번호',
|
pricingBasis: '가격 참고 기준',
|
||||||
|
checkedAt: '기준일',
|
||||||
|
itemCondition: '제품 상태',
|
||||||
|
priceVerified: '가격 확인',
|
||||||
extension: '추가 콘텐츠',
|
extension: '추가 콘텐츠',
|
||||||
none: '없음',
|
none: '없음',
|
||||||
supported: '대응',
|
supported: '대응',
|
||||||
@@ -42,7 +50,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
},
|
},
|
||||||
ja: {
|
ja: {
|
||||||
infoTitle: 'Infomation',
|
infoTitle: 'Infomation',
|
||||||
purchaseTitle: '購入情報',
|
purchaseTitle: '販売情報',
|
||||||
requiredCapacity: '必要な容量',
|
requiredCapacity: '必要な容量',
|
||||||
playMode: 'プレイモード',
|
playMode: 'プレイモード',
|
||||||
playUser: 'プレイ人数',
|
playUser: 'プレイ人数',
|
||||||
@@ -55,10 +63,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
cero: 'CERO',
|
cero: 'CERO',
|
||||||
iarc: 'IARC',
|
iarc: 'IARC',
|
||||||
releaseDate: '配信日',
|
releaseDate: '配信日',
|
||||||
purchaseDate: '購入日',
|
suggestedPrice: '推奨販売価格',
|
||||||
store: '購入先',
|
pricePending: '-',
|
||||||
price: '購入価格',
|
priceRange: '価格帯',
|
||||||
orderNumber: '注文番号',
|
pricingBasis: '価格参考基準',
|
||||||
|
checkedAt: '確認日',
|
||||||
|
itemCondition: '商品状態',
|
||||||
|
priceVerified: '価格確認',
|
||||||
extension: '追加コンテンツ',
|
extension: '追加コンテンツ',
|
||||||
none: 'なし',
|
none: 'なし',
|
||||||
supported: '対応',
|
supported: '対応',
|
||||||
@@ -68,11 +79,178 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
const currentTexts = texts[language];
|
const currentTexts = texts[language];
|
||||||
|
|
||||||
|
function formatKRW(value) {
|
||||||
|
if (typeof value !== 'number') return currentTexts.pricePending;
|
||||||
|
return `${value.toLocaleString('ko-KR')}원`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPricingBasis(pricingBasis) {
|
||||||
|
const labels = {
|
||||||
|
ko: {
|
||||||
|
KR_USED_REFERENCE_ESTIMATE: '국내 중고 시세 참고',
|
||||||
|
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '일본 중고 시세 환산',
|
||||||
|
MANUAL: '직접 입력가',
|
||||||
|
SOLD_HISTORY: '판매 이력 기준',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
KR_USED_REFERENCE_ESTIMATE: '韓国中古相場参考',
|
||||||
|
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '日本中古相場換算',
|
||||||
|
MANUAL: '手入力価格',
|
||||||
|
SOLD_HISTORY: '販売履歴基準',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[language][pricingBasis] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatItemCondition(itemCondition) {
|
||||||
|
if (!itemCondition || itemCondition === '-') return '';
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
ko: {
|
||||||
|
SEALED: '미개봉',
|
||||||
|
OPENED: '개봉',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
SEALED: '未開封',
|
||||||
|
OPENED: '開封済',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[language][itemCondition] || itemCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPriceVerified(priceVerified) {
|
||||||
|
if (!priceVerified) return '';
|
||||||
|
return language === 'ko' ? '실제 시세 확인 완료' : '実勢価格確認済み';
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertLanguage(value) {
|
||||||
|
if (!value || language !== 'ko') return value;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
日本語: '일본어',
|
||||||
|
英語: '영어',
|
||||||
|
韓国語: '한국어',
|
||||||
|
フランス語: '프랑스어',
|
||||||
|
ドイツ語: '독일어',
|
||||||
|
イタリア語: '이탈리아어',
|
||||||
|
スペイン語: '스페인어',
|
||||||
|
ロシア語: '러시아어',
|
||||||
|
オランダ語: '네덜란드어',
|
||||||
|
ポルトガル語: '포르투갈어',
|
||||||
|
'中国語 (簡体字)': '중국어 (간체)',
|
||||||
|
'中国語 (繁体字)': '중국어 (번체)',
|
||||||
|
};
|
||||||
|
|
||||||
|
return value
|
||||||
|
.split(',')
|
||||||
|
.map(item => labels[item.trim()] || item.trim())
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertMaker(value) {
|
||||||
|
if (!value || language !== 'ko') return value;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
任天堂: '닌텐도',
|
||||||
|
スクウェア・エニックス: '스퀘어 에닉스',
|
||||||
|
コーエーテクモゲームス: '코에이 테크모 게임스',
|
||||||
|
ポケモン: '포켓몬',
|
||||||
|
バンダイナムコエンターテインメント: '반다이 남코 엔터테인먼트',
|
||||||
|
アトラス: '아틀러스',
|
||||||
|
セガ: '세가',
|
||||||
|
マーベラス: '마블러스 엔터테인먼트',
|
||||||
|
カプコン: '캡콤',
|
||||||
|
ユービーアイソフト: '유비소프트',
|
||||||
|
日本一ソフトウェア: '니폰이치 소프트웨어',
|
||||||
|
アークシステムワークス: '아크 시스템 웍스',
|
||||||
|
日本ファルコム: '일본 팔콤',
|
||||||
|
};
|
||||||
|
|
||||||
|
return value
|
||||||
|
.split(',')
|
||||||
|
.map(item => labels[item.trim()] || item.trim())
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertTags(value) {
|
||||||
|
if (!value || language !== 'ko') return value;
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
アクション: '액션',
|
||||||
|
アドベンチャー: '어드벤처',
|
||||||
|
テキストアドベンチャー: '텍스트 어드벤처',
|
||||||
|
ロールプレイング: '롤플레잉',
|
||||||
|
シューティング: '슈팅',
|
||||||
|
シミュレーション: '시뮬레이션',
|
||||||
|
ストラテジー: '전략',
|
||||||
|
パズル: '퍼즐',
|
||||||
|
レース: '레이스',
|
||||||
|
スポーツ: '스포츠',
|
||||||
|
格闘: '격투',
|
||||||
|
音楽ゲーム: '음악 게임',
|
||||||
|
恋愛: '연애',
|
||||||
|
難易度が選べる: '난이도 선택 가능',
|
||||||
|
戦うたびに強くなる: '싸울수록 성장',
|
||||||
|
キャラクターボイス: '캐릭터 음성',
|
||||||
|
オンラインで対戦: '온라인 대전',
|
||||||
|
オンラインで協力: '온라인 협력',
|
||||||
|
オンラインでフレンドと: '친구와 온라인으로',
|
||||||
|
キャラクターカスタマイズ: '캐릭터 커스터마이즈',
|
||||||
|
'3人称視点': '3인칭 시점',
|
||||||
|
'1台の本体でいっしょにあそべる': '한 대의 본체에서 함께 플레이',
|
||||||
|
ともだちや家族と集まって: '친구와 가족과 함께',
|
||||||
|
オンラインランキング: '온라인 랭킹',
|
||||||
|
本体を持ちよってあそべる: '게임기를 들고 플레이',
|
||||||
|
世界を自由にかけ回る: '세계를 자유롭게 탐험',
|
||||||
|
目的はあなた次第: '목적은 자유롭게 선택',
|
||||||
|
};
|
||||||
|
|
||||||
|
return value
|
||||||
|
.split(',')
|
||||||
|
.map(item => labels[item.trim()] || item.trim())
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertReleaseDate(value) {
|
||||||
|
if (!value || language !== 'ko') return value;
|
||||||
|
|
||||||
|
const date = new Date(value.replace(/年|月/g, '/').replace('日', ''));
|
||||||
|
if (Number.isNaN(date.getTime())) return value;
|
||||||
|
|
||||||
|
return `${date.getFullYear()}년 ${date.getMonth() + 1}월 ${date.getDate()}일`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertSupportValue(value) {
|
||||||
|
if (!value || language !== 'ko') return value;
|
||||||
|
|
||||||
|
return value.replaceAll('対応', '대응').replaceAll('非対応', '비대응');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGameInfoValue(key) {
|
||||||
|
switch (key) {
|
||||||
|
case 'maker':
|
||||||
|
return convertMaker(game[key]);
|
||||||
|
case 'language':
|
||||||
|
return convertLanguage(game[key]);
|
||||||
|
case 'release':
|
||||||
|
return convertReleaseDate(game[key]);
|
||||||
|
case 'onlineDataSave':
|
||||||
|
return convertSupportValue(game[key]);
|
||||||
|
default:
|
||||||
|
return game[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 기본 정보 설정
|
// 기본 정보 설정
|
||||||
document.getElementById('gameImage').src = window.innerWidth < 640 ? game.thumbnail : game.image;
|
document.getElementById('gameImage').src = window.innerWidth < 640 ? game.thumbnail : game.image;
|
||||||
document.getElementById('gameTitle').textContent =
|
const displayTitle = language === 'ko' ? game.koTitle || game.title : game.title;
|
||||||
language === 'ko' ? game.koTitle || game.title : game.title;
|
const gameTitleEl = document.getElementById('gameTitle');
|
||||||
document.getElementById('gameTags').textContent = game.tags;
|
gameTitleEl.className =
|
||||||
|
'text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl flex items-center justify-center gap-2';
|
||||||
|
gameTitleEl.innerHTML = `${game.sale?.priceVerified ? `${VERIFIED_PRICE_MARK} ` : ''}<span>${displayTitle}</span>`;
|
||||||
|
document.getElementById('gameTags').textContent = convertTags(game.tags);
|
||||||
document.getElementById('infoTitle').textContent = currentTexts.infoTitle;
|
document.getElementById('infoTitle').textContent = currentTexts.infoTitle;
|
||||||
document.getElementById('purchaseTitle').textContent = currentTexts.purchaseTitle;
|
document.getElementById('purchaseTitle').textContent = currentTexts.purchaseTitle;
|
||||||
document.getElementById('purchaseGameTitle').textContent =
|
document.getElementById('purchaseGameTitle').textContent =
|
||||||
@@ -80,14 +258,18 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// 게임 상태 설정
|
// 게임 상태 설정
|
||||||
const statusClass = {
|
const statusClass = {
|
||||||
package: 'bg-green-100 text-green-800',
|
available: 'bg-green-100 text-green-800',
|
||||||
download: 'bg-yellow-100 text-yellow-800',
|
download: 'bg-yellow-100 text-yellow-800',
|
||||||
expansion: 'bg-blue-100 text-blue-800',
|
expansion: 'bg-blue-100 text-blue-800',
|
||||||
|
sold: 'bg-red-100 text-red-800',
|
||||||
}[game.status];
|
}[game.status];
|
||||||
document.getElementById(
|
document.getElementById(
|
||||||
'gameStatus',
|
'gameStatus',
|
||||||
).className = `inline-flex rounded-full px-2 text-xs font-semibold leading-5 ${statusClass}`;
|
).className = `inline-flex rounded-full px-2 text-xs font-semibold leading-5 ${statusClass}`;
|
||||||
document.getElementById('gameStatus').textContent = game.status;
|
document.getElementById('gameStatus').textContent =
|
||||||
|
language === 'ko'
|
||||||
|
? { available: '판매중', sold: '판매완료' }[game.status] || game.status
|
||||||
|
: { available: '販売中', sold: '販売済み' }[game.status] || game.status;
|
||||||
|
|
||||||
// 국가 설정
|
// 국가 설정
|
||||||
const countryClass =
|
const countryClass =
|
||||||
@@ -98,11 +280,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.getElementById('gameCountry').textContent =
|
document.getElementById('gameCountry').textContent =
|
||||||
language === 'ko'
|
language === 'ko'
|
||||||
? game.country === 'JPN'
|
? game.country === 'JPN'
|
||||||
? '일본판'
|
? '🇯🇵 일본판'
|
||||||
: '한국판'
|
: '🇰🇷 한국 정발판'
|
||||||
: game.country === 'JPN'
|
: game.country === 'JPN'
|
||||||
? '日本版'
|
? '🇯🇵 日本版'
|
||||||
: '韓国版';
|
: '🇰🇷 韓国版';
|
||||||
|
|
||||||
// 게임 정보 설정
|
// 게임 정보 설정
|
||||||
const gameInfo = document.getElementById('gameInfo');
|
const gameInfo = document.getElementById('gameInfo');
|
||||||
@@ -116,41 +298,51 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
{ key: 'language', label: currentTexts.language },
|
{ key: 'language', label: currentTexts.language },
|
||||||
{ key: 'cero', label: currentTexts.cero },
|
{ key: 'cero', label: currentTexts.cero },
|
||||||
{ key: 'iarc', label: currentTexts.iarc },
|
{ key: 'iarc', label: currentTexts.iarc },
|
||||||
{ key: 'releaseDate', label: currentTexts.releaseDate },
|
{ key: 'release', label: currentTexts.releaseDate },
|
||||||
];
|
];
|
||||||
|
|
||||||
infoItems.forEach(item => {
|
infoItems.forEach(item => {
|
||||||
if (game[item.key]) {
|
const value = getGameInfoValue(item.key);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'border-t border-gray-200 pt-4';
|
div.className = 'border-t border-gray-200 pt-4';
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<dt class="font-medium text-gray-900">${item.label}</dt>
|
<dt class="font-medium text-gray-900">${item.label}</dt>
|
||||||
<dd class="mt-2 text-sm text-gray-500">${game[item.key]}</dd>
|
<dd class="mt-2 text-sm text-gray-500">${value}</dd>
|
||||||
`;
|
`;
|
||||||
gameInfo.appendChild(div);
|
gameInfo.appendChild(div);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 구매 정보 설정
|
// 판매 정보 설정
|
||||||
const purchaseInfo = document.getElementById('purchaseInfo');
|
const purchaseInfo = document.getElementById('purchaseInfo');
|
||||||
if (game.purchaseInformation) {
|
if (game.sale) {
|
||||||
const purchaseItems = [
|
const priceRange =
|
||||||
{ key: 'date', label: currentTexts.purchaseDate },
|
game.sale.priceRange?.min && game.sale.priceRange?.max
|
||||||
{ key: 'store', label: currentTexts.store },
|
? `${formatKRW(game.sale.priceRange.min)} ~ ${formatKRW(game.sale.priceRange.max)}`
|
||||||
{ key: 'price', label: currentTexts.price },
|
: '';
|
||||||
{ key: 'orderNumber', label: currentTexts.orderNumber },
|
const saleItems = [
|
||||||
|
{ value: formatKRW(game.sale.suggestedPrice), label: currentTexts.suggestedPrice },
|
||||||
|
...(SHOW_RECOMMENDED_PRICE_RANGE_BY_DEFAULT
|
||||||
|
? [{ value: priceRange, label: currentTexts.priceRange }]
|
||||||
|
: []),
|
||||||
|
{ value: formatPricingBasis(game.sale.pricingBasis), label: currentTexts.pricingBasis },
|
||||||
|
{ value: formatItemCondition(game.itemCondition), label: currentTexts.itemCondition },
|
||||||
|
{ value: formatPriceVerified(game.sale.priceVerified), label: currentTexts.priceVerified },
|
||||||
|
{ value: game.sale.checkedAt, label: currentTexts.checkedAt },
|
||||||
];
|
];
|
||||||
|
|
||||||
const dl = document.createElement('dl');
|
const dl = document.createElement('dl');
|
||||||
dl.className = 'grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2';
|
dl.className = 'grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2';
|
||||||
|
|
||||||
purchaseItems.forEach(item => {
|
saleItems.forEach(item => {
|
||||||
if (game.purchaseInformation[item.key]) {
|
if (item.value) {
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'sm:col-span-1';
|
div.className = 'sm:col-span-1';
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<dt class="text-sm font-medium text-gray-500">${item.label}</dt>
|
<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>
|
<dd class="mt-1 text-sm text-gray-900">${item.value}</dd>
|
||||||
`;
|
`;
|
||||||
dl.appendChild(div);
|
dl.appendChild(div);
|
||||||
}
|
}
|
||||||
|
|||||||
571
script/nsw.js
571
script/nsw.js
@@ -1,4 +1,12 @@
|
|||||||
import NSW_DB from '../db/nsw.db.js';
|
import NSW_DB from '../db/nsw.resale.db.js?v=20260521-badges-v2';
|
||||||
|
|
||||||
|
window.NSW_APP_VERSION = '20260521-badges-v2';
|
||||||
|
|
||||||
|
const SHOW_SOLD_BY_DEFAULT = false;
|
||||||
|
const SHOW_RECOMMENDED_PRICE_RANGE_BY_DEFAULT = false;
|
||||||
|
|
||||||
|
/** @type {string} 가격 확인 완료 표시 */
|
||||||
|
const VERIFIED_PRICE_MARK = '⭐';
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const gameList = document.getElementById('gameList');
|
const gameList = document.getElementById('gameList');
|
||||||
@@ -9,27 +17,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 언어별 UI 텍스트
|
// 언어별 UI 텍스트
|
||||||
const uiTexts = {
|
const uiTexts = {
|
||||||
ko: {
|
ko: {
|
||||||
title: 'Switch DB',
|
title: 'Switch 패키지 판매 목록',
|
||||||
languageSelect: '언어 선택',
|
languageSelect: '언어 선택',
|
||||||
languageDescription: '선택한 언어로 표시됩니다',
|
languageDescription: '선택한 언어로 표시됩니다',
|
||||||
korean: '한국어',
|
korean: '한국어',
|
||||||
japanese: '일본어',
|
japanese: '일본어',
|
||||||
count: '개수',
|
count: '개수',
|
||||||
|
listIntro: '우선 이런 게임들이 있습니다. 가격은 순차적으로 작성중입니다.',
|
||||||
|
pricePending: '-',
|
||||||
loading: '로딩중...',
|
loading: '로딩중...',
|
||||||
tableHeaders: {
|
tableHeaders: {
|
||||||
title: '제목',
|
title: '제목',
|
||||||
info: '정보',
|
info: '판매가',
|
||||||
status: '상태',
|
status: '판매 상태',
|
||||||
role: '역할',
|
|
||||||
location: '지역'
|
|
||||||
},
|
},
|
||||||
sortOptions: {
|
sortOptions: {
|
||||||
sortByNoDesc: '순번 최신순',
|
|
||||||
sortByNo: '순번 과거순',
|
|
||||||
sortByDateDesc: '발매일 최신순',
|
sortByDateDesc: '발매일 최신순',
|
||||||
sortByDate: '발매일 과거순',
|
sortByDate: '발매일 과거순',
|
||||||
sortByPurchaseDateDesc: '구매일 최신순',
|
sortByPriceDesc: '판매가 높은순',
|
||||||
sortByPurchaseDate: '구매일 과거순',
|
sortByPrice: '판매가 낮은순',
|
||||||
sortByRandom: '무작위',
|
sortByRandom: '무작위',
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
@@ -39,27 +45,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
ja: {
|
ja: {
|
||||||
title: 'ゲーム一覧',
|
title: '中古販売リスト',
|
||||||
languageSelect: '言語選択',
|
languageSelect: '言語選択',
|
||||||
languageDescription: '選択した言語で表示されます',
|
languageDescription: '選択した言語で表示されます',
|
||||||
korean: '韓国語',
|
korean: '韓国語',
|
||||||
japanese: '日本語',
|
japanese: '日本語',
|
||||||
count: '件数',
|
count: '件数',
|
||||||
|
listIntro: 'まずは所持タイトルを掲載しています。価格は順次入力中です。',
|
||||||
|
pricePending: '-',
|
||||||
loading: '読み込み中...',
|
loading: '読み込み中...',
|
||||||
tableHeaders: {
|
tableHeaders: {
|
||||||
title: 'タイトル',
|
title: 'タイトル',
|
||||||
info: '情報',
|
info: '販売価格',
|
||||||
status: '状態',
|
status: '販売状態',
|
||||||
role: '役割',
|
|
||||||
location: '地域'
|
|
||||||
},
|
},
|
||||||
sortOptions: {
|
sortOptions: {
|
||||||
sortByNoDesc: '番号降順',
|
|
||||||
sortByNo: '番号昇順',
|
|
||||||
sortByDateDesc: '発売日降順',
|
sortByDateDesc: '発売日降順',
|
||||||
sortByDate: '発売日昇順',
|
sortByDate: '発売日昇順',
|
||||||
sortByPurchaseDateDesc: '購入日降順',
|
sortByPriceDesc: '販売価格降順',
|
||||||
sortByPurchaseDate: '購入日昇順',
|
sortByPrice: '販売価格昇順',
|
||||||
sortByRandom: 'ランダム',
|
sortByRandom: 'ランダム',
|
||||||
},
|
},
|
||||||
filter: {
|
filter: {
|
||||||
@@ -79,20 +83,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
country: [],
|
country: [],
|
||||||
cero: [],
|
cero: [],
|
||||||
},
|
},
|
||||||
sortBy: 'sortByNoDesc', // 기본 정렬 옵션
|
searchText: '',
|
||||||
|
sortBy: 'sortByRandom',
|
||||||
};
|
};
|
||||||
|
|
||||||
// 정렬 옵션 정의
|
|
||||||
const sortOptions = [
|
|
||||||
{ name: '순번 최신순', value: 'sortByNoDesc' },
|
|
||||||
{ name: '순번 과거순', value: 'sortByNo' },
|
|
||||||
{ name: '발매일 최신순', value: 'sortByDateDesc' },
|
|
||||||
{ name: '발매일 과거순', value: 'sortByDate' },
|
|
||||||
{ name: '구매일 최신순', value: 'sortByPurchaseDateDesc' },
|
|
||||||
{ name: '구매일 과거순', value: 'sortByPurchaseDate' },
|
|
||||||
{ name: '무작위', value: 'sortByRandom' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// UI 텍스트 업데이트 함수
|
// UI 텍스트 업데이트 함수
|
||||||
function updateUITexts() {
|
function updateUITexts() {
|
||||||
const texts = uiTexts[filterState.language];
|
const texts = uiTexts[filterState.language];
|
||||||
@@ -101,38 +95,44 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
document.querySelector('h1').textContent = texts.title;
|
document.querySelector('h1').textContent = texts.title;
|
||||||
|
|
||||||
// 언어 선택 섹션 업데이트
|
// 언어 선택 섹션 업데이트
|
||||||
document.querySelector('label.text-base').textContent = texts.languageSelect;
|
setTextContent('label.text-base', texts.languageSelect);
|
||||||
document.querySelector('p.text-sm').textContent = texts.languageDescription;
|
setTextContent('p.text-sm', texts.languageDescription);
|
||||||
document.querySelector('label[for="ko"]').textContent = texts.korean;
|
setTextContent('label[for="ko"]', texts.korean);
|
||||||
document.querySelector('label[for="ja"]').textContent = texts.japanese;
|
setTextContent('label[for="ja"]', texts.japanese);
|
||||||
|
|
||||||
// 테이블 헤더 업데이트
|
// 테이블 헤더 업데이트
|
||||||
const headers = document.querySelectorAll('th');
|
const headers = document.querySelectorAll('th');
|
||||||
headers[0].textContent = texts.tableHeaders.title;
|
if (headers[0]) headers[0].textContent = texts.tableHeaders.title;
|
||||||
headers[1].textContent = texts.tableHeaders.info;
|
if (headers[1]) headers[1].textContent = texts.tableHeaders.info;
|
||||||
headers[2].textContent = texts.tableHeaders.status;
|
if (headers[2]) headers[2].textContent = texts.tableHeaders.status;
|
||||||
headers[3].textContent = texts.tableHeaders.role;
|
|
||||||
headers[4].textContent = texts.tableHeaders.location;
|
|
||||||
|
|
||||||
// 로딩 텍스트 업데이트
|
// 로딩 텍스트 업데이트
|
||||||
loading.textContent = texts.loading;
|
loading.textContent = texts.loading;
|
||||||
|
setTextContent('#priceProgressNotice', texts.listIntro);
|
||||||
|
|
||||||
// 필터 텍스트 업데이트
|
// 필터 텍스트 업데이트
|
||||||
document.getElementById('resetFilters').textContent = texts.filter.reset;
|
setTextContent('#resetFilters', texts.filter.reset);
|
||||||
document.querySelector('label[for="korean-support"]').textContent = texts.filter.koreanSupport;
|
setTextContent('label[for="korean-support"]', texts.filter.koreanSupport);
|
||||||
document.querySelector('label[for="korean-not-support"]').textContent =
|
setTextContent('label[for="korean-not-support"]', texts.filter.koreanNotSupport);
|
||||||
texts.filter.koreanNotSupport;
|
}
|
||||||
|
|
||||||
|
function setTextContent(selector, text) {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (element) element.textContent = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 언어 변경 이벤트 리스너
|
// 언어 변경 이벤트 리스너
|
||||||
languageRadios.forEach(radio => {
|
languageRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', e => {
|
radio.addEventListener('change', e => {
|
||||||
filterState.language = e.target.value;
|
filterState.language = e.target.value;
|
||||||
|
localStorage.setItem('language', filterState.language);
|
||||||
updateUITexts();
|
updateUITexts();
|
||||||
renderGames();
|
renderGames();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
localStorage.setItem('language', filterState.language);
|
||||||
|
|
||||||
// 언어 변환 함수
|
// 언어 변환 함수
|
||||||
function convertLanguage(language) {
|
function convertLanguage(language) {
|
||||||
if (!language) return '';
|
if (!language) return '';
|
||||||
@@ -581,34 +581,183 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatGameData(game) {
|
function formatGameData(game) {
|
||||||
|
const suggestedPrice = game.sale?.suggestedPrice;
|
||||||
|
const priceRange = game.sale?.priceRange;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...game,
|
...game,
|
||||||
formattedTitle: filterState.language === 'ko' ? game.koTitle || game.title : game.title,
|
formattedTitle: filterState.language === 'ko' ? game.koTitle || game.title : game.title,
|
||||||
formattedReleaseDate: convertReleaseDate(game.release),
|
|
||||||
formattedMaker: convertMaker(game.maker),
|
formattedMaker: convertMaker(game.maker),
|
||||||
formattedLanguage: convertLanguage(game.language),
|
formattedLanguage: convertLanguage(game.language),
|
||||||
formattedTags: convertTags(game.tags),
|
formattedTags: convertTags(game.tags),
|
||||||
formattedCountry: convertCountry(game.country),
|
formattedCountry: convertCountry(game.country),
|
||||||
|
formattedSuggestedPrice: formatKRW(suggestedPrice),
|
||||||
|
formattedPriceRange:
|
||||||
|
priceRange?.min && priceRange?.max
|
||||||
|
? `${formatKRW(priceRange.min)} ~ ${formatKRW(priceRange.max)}`
|
||||||
|
: '',
|
||||||
|
formattedPricingBasis: formatPricingBasis(game.sale?.pricingBasis),
|
||||||
|
formattedConfidenceDescription: formatConfidenceDescription(game.sale?.confidence),
|
||||||
|
formattedSaleStatus: formatSaleStatus(game.status),
|
||||||
|
formattedItemCondition: formatItemCondition(game.itemCondition),
|
||||||
|
formattedEdition: formatEdition(game.country),
|
||||||
|
formattedKoreanSupport: formatKoreanSupport(game.language),
|
||||||
|
hasItemCondition: Boolean(formatItemCondition(game.itemCondition)),
|
||||||
|
hasKoreanSupport: hasKoreanLanguage(game.language),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusClass(status) {
|
function formatItemCondition(itemCondition) {
|
||||||
|
if (!itemCondition || itemCondition === '-') return '';
|
||||||
|
|
||||||
|
const labels = {
|
||||||
|
ko: {
|
||||||
|
SEALED: '미개봉',
|
||||||
|
OPENED: '개봉',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
SEALED: '未開封',
|
||||||
|
OPENED: '開封済',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[filterState.language][itemCondition] || itemCondition;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatEdition(country) {
|
||||||
|
if (country === 'JPN') {
|
||||||
|
return filterState.language === 'ko' ? '🇯🇵 일본판' : '🇯🇵 日本版';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (country === 'KOR') {
|
||||||
|
return filterState.language === 'ko' ? '🇰🇷 한국 정발판' : '🇰🇷 韓国版';
|
||||||
|
}
|
||||||
|
|
||||||
|
return country || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasKoreanLanguage(language) {
|
||||||
|
return Boolean(language?.includes('韓国語') || language?.includes('한국어'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKoreanSupport(language) {
|
||||||
|
const isSupported = hasKoreanLanguage(language);
|
||||||
|
|
||||||
|
if (filterState.language === 'ko') {
|
||||||
|
return isSupported ? '🇰🇷 한국어 지원' : '❌ 한국어 미지원';
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSupported ? '🇰🇷 韓国語対応' : '❌ 韓国語非対応';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatKRW(value) {
|
||||||
|
if (typeof value !== 'number') return uiTexts[filterState.language].pricePending;
|
||||||
|
return `${value.toLocaleString('ko-KR')}원`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSuggestedPriceValue(game) {
|
||||||
|
const suggestedPrice = game.sale?.suggestedPrice;
|
||||||
|
return typeof suggestedPrice === 'number' ? suggestedPrice : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatSaleStatus(status) {
|
||||||
|
const labels = {
|
||||||
|
ko: {
|
||||||
|
available: '판매 가능',
|
||||||
|
sold: '판매완료',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
available: '販売可',
|
||||||
|
sold: '販売済み',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[filterState.language][status] || status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatPricingBasis(pricingBasis) {
|
||||||
|
const labels = {
|
||||||
|
ko: {
|
||||||
|
KR_USED_REFERENCE_ESTIMATE: '국내 중고 시세 참고',
|
||||||
|
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '일본 중고 시세 환산',
|
||||||
|
MANUAL: '직접 입력가',
|
||||||
|
SOLD_HISTORY: '판매 이력 기준',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
KR_USED_REFERENCE_ESTIMATE: '韓国中古相場参考',
|
||||||
|
JP_USED_REFERENCE_CONVERTED_TO_KRW_ESTIMATE: '日本中古相場換算',
|
||||||
|
MANUAL: '手入力価格',
|
||||||
|
SOLD_HISTORY: '販売履歴基準',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return labels[filterState.language][pricingBasis] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatConfidenceDescription(confidence) {
|
||||||
|
const descriptions = {
|
||||||
|
ko: {
|
||||||
|
high: '최근 거래가가 안정적인 편이라 표시가에 가까운 거래를 기대할 수 있습니다.',
|
||||||
|
medium: '중고 시세를 참고한 합리적인 기준가이며 상태에 따라 소폭 조정될 수 있습니다.',
|
||||||
|
'medium-low': '판본, 언어, 구성품에 따라 가격 차이가 있어 확인 후 조정 가능합니다.',
|
||||||
|
low: '거래 사례가 적은 타이틀이라 상태 확인 후 가격 협의 여지가 있습니다.',
|
||||||
|
},
|
||||||
|
ja: {
|
||||||
|
high: '最近の取引価格が安定しており、表示価格に近い取引が期待できます。',
|
||||||
|
medium: '中古相場を参考にした基準価格で、状態により多少調整できます。',
|
||||||
|
'medium-low': '版、言語、付属品により価格差があるため、確認後に調整できます。',
|
||||||
|
low: '取引例が少ないタイトルのため、状態確認後に価格相談できます。',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return descriptions[filterState.language][confidence] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSaleStatusClass(status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 'package':
|
case 'sold':
|
||||||
return 'bg-green-100 text-green-800';
|
return 'bg-red-100 text-red-800';
|
||||||
case 'download':
|
|
||||||
return 'bg-yellow-100 text-yellow-800';
|
|
||||||
case 'expansion':
|
|
||||||
return 'bg-blue-100 text-blue-800';
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return 'bg-green-100 text-green-800';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCountryClass(country) {
|
function getItemConditionClass(itemCondition) {
|
||||||
return country === 'JPN'
|
return 'bg-amber-100 text-amber-800';
|
||||||
? 'text-red-600 hover:text-red-900'
|
}
|
||||||
: 'text-indigo-600 hover:text-indigo-900';
|
|
||||||
|
function getEditionClass(country) {
|
||||||
|
return country === 'JPN' ? 'bg-red-100 text-red-800' : 'bg-indigo-100 text-indigo-800';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getKoreanSupportClass(hasKoreanSupport) {
|
||||||
|
return hasKoreanSupport ? 'bg-emerald-100 text-emerald-800' : 'bg-gray-100 text-gray-600';
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderInfoBadges(game, justifyClass = '') {
|
||||||
|
return `
|
||||||
|
<div class="flex flex-wrap items-center gap-1.5 ${justifyClass}">
|
||||||
|
${
|
||||||
|
game.hasItemCondition
|
||||||
|
? `<span class="rounded-full px-2 text-xs font-semibold leading-5 ${getItemConditionClass(
|
||||||
|
game.itemCondition,
|
||||||
|
)}">
|
||||||
|
${game.formattedItemCondition}
|
||||||
|
</span>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
<span class="rounded-full px-2 text-xs font-semibold leading-5 ${getEditionClass(
|
||||||
|
game.country,
|
||||||
|
)}">
|
||||||
|
${game.formattedEdition}
|
||||||
|
</span>
|
||||||
|
<span class="rounded-full px-2 text-xs font-semibold leading-5 ${getKoreanSupportClass(
|
||||||
|
game.hasKoreanSupport,
|
||||||
|
)}">
|
||||||
|
${game.formattedKoreanSupport}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createGameRow(game, index) {
|
function createGameRow(game, index) {
|
||||||
@@ -622,40 +771,57 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td class="py-4 pl-4 pr-3 text-sm">
|
<td class="py-4 pl-4 pr-3 text-sm">
|
||||||
<div class="flex items-center">
|
<div class="flex items-start">
|
||||||
<div class="h-10 w-10 flex-shrink-0">
|
<div class="h-10 w-10 flex-shrink-0">
|
||||||
<img class="h-10 w-10 rounded-xl object-cover" src="${game.thumbnail}" alt="" />
|
<img class="h-10 w-10 rounded-xl object-cover" src="${game.thumbnail}" alt="" />
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-4 min-w-0 flex-1">
|
||||||
<div class="font-medium text-gray-900 ${
|
<div class="font-medium leading-5 text-gray-900 flex items-center gap-1">
|
||||||
game.status === 'sold' ? 'text-red-600 line-through' : ''
|
${game.sale?.priceVerified ? `${VERIFIED_PRICE_MARK} ` : ''}<span>${game.formattedTitle}</span>
|
||||||
}">
|
</div>
|
||||||
${game.no}. ${game.formattedTitle}
|
<div class="mt-2">
|
||||||
|
${renderInfoBadges(game)}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 max-w-lg text-xs leading-5 text-gray-500">
|
||||||
|
${game.formattedConfidenceDescription}
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 space-y-2 sm:hidden">
|
||||||
|
<div class="flex flex-wrap items-center gap-x-3 gap-y-1">
|
||||||
|
<span class="text-base font-semibold leading-6 text-gray-900">
|
||||||
|
${game.formattedSuggestedPrice || '-'}
|
||||||
|
</span>
|
||||||
|
<span class="rounded-full px-2 text-xs font-semibold leading-5 ${getSaleStatusClass(
|
||||||
|
game.status,
|
||||||
|
)}">
|
||||||
|
${game.formattedSaleStatus}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs leading-5 text-gray-500">
|
||||||
|
${game.formattedPricingBasis}
|
||||||
|
</div>
|
||||||
|
${
|
||||||
|
SHOW_RECOMMENDED_PRICE_RANGE_BY_DEFAULT
|
||||||
|
? `<div class="text-xs leading-5 text-gray-500">
|
||||||
|
${filterState.language === 'ko' ? '가격 범위' : '価格帯'}:
|
||||||
|
${game.formattedPriceRange || '-'}
|
||||||
|
</div>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-500">${game.formattedReleaseDate}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="hidden w-3/12 px-3 py-4 text-sm text-gray-500 lg:table-cell">
|
<td class="hidden w-56 px-3 py-4 text-sm text-gray-500 sm:table-cell">
|
||||||
<div class="text-gray-900">${game.formattedMaker}</div>
|
<div class="font-semibold leading-5 text-gray-900">
|
||||||
<div class="text-gray-500">${game.formattedLanguage}</div>
|
${game.formattedSuggestedPrice || '-'}
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 text-xs text-gray-400">${game.formattedPricingBasis}</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-4 text-sm text-gray-500 mx-auto">
|
<td class="hidden w-28 px-3 py-4 text-sm text-gray-500 mx-auto sm:table-cell">
|
||||||
<div class="flex justify-center rounded-full px-2 text-xs font-semibold leading-5 ${getStatusClass(
|
<div class="flex justify-center whitespace-nowrap rounded-full px-2 text-xs font-semibold leading-5 ${getSaleStatusClass(
|
||||||
game.status,
|
game.status,
|
||||||
)}">
|
)}">
|
||||||
${game.status}
|
${game.formattedSaleStatus}
|
||||||
</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>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
@@ -677,16 +843,152 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
country: [],
|
country: [],
|
||||||
cero: [],
|
cero: [],
|
||||||
};
|
};
|
||||||
|
filterState.searchText = '';
|
||||||
|
document.querySelectorAll('[data-search-input]').forEach(input => {
|
||||||
|
input.value = '';
|
||||||
|
});
|
||||||
|
|
||||||
// 게임 목록 다시 렌더링
|
// 게임 목록 다시 렌더링
|
||||||
renderGames();
|
renderGames();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 필터 초기화 버튼 이벤트 리스너 설정
|
// 필터 초기화 버튼 이벤트 리스너 설정
|
||||||
document.getElementById('resetFilters').addEventListener('click', resetFilters);
|
document.getElementById('resetFilters')?.addEventListener('click', resetFilters);
|
||||||
|
|
||||||
// 필터 체크박스 이벤트 리스너 설정
|
// 필터 체크박스 이벤트 리스너 설정
|
||||||
function setupFilterListeners() {
|
function setupFilterListeners() {
|
||||||
|
const searchInputs = [
|
||||||
|
document.getElementById('search-input'),
|
||||||
|
document.getElementById('mobile-search-input'),
|
||||||
|
].filter(Boolean);
|
||||||
|
const searchForms = [document.getElementById('searchForm')].filter(Boolean);
|
||||||
|
const resetSearchButtons = [
|
||||||
|
document.getElementById('reset-search'),
|
||||||
|
document.getElementById('mobile-reset-search'),
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
function updateSearchText(value) {
|
||||||
|
filterState.searchText = value.trim().toLowerCase();
|
||||||
|
searchInputs.forEach(input => {
|
||||||
|
if (input.value !== value) input.value = value;
|
||||||
|
});
|
||||||
|
renderGames();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.updateNswSearch = value => {
|
||||||
|
updateSearchText(value || '');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.clearNswSearch = () => {
|
||||||
|
updateSearchText('');
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateSearchFromInput(input, options = {}) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
updateSearchText(input.value);
|
||||||
|
if (options.blur) input.blur();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSearchInput(target) {
|
||||||
|
return target instanceof HTMLInputElement && target.matches('[data-search-input]');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'submit',
|
||||||
|
event => {
|
||||||
|
if (!event.target.querySelector?.('[data-search-input]')) return;
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
|
||||||
|
event.stopPropagation();
|
||||||
|
const searchInput = event.target.querySelector('[data-search-input]');
|
||||||
|
if (searchInput) updateSearchFromInput(searchInput, { blur: true });
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'input',
|
||||||
|
event => {
|
||||||
|
if (!isSearchInput(event.target)) return;
|
||||||
|
updateSearchText(event.target.value);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'change',
|
||||||
|
event => {
|
||||||
|
if (!isSearchInput(event.target)) return;
|
||||||
|
updateSearchFromInput(event.target);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
document.addEventListener(
|
||||||
|
'focusout',
|
||||||
|
event => {
|
||||||
|
if (!isSearchInput(event.target)) return;
|
||||||
|
updateSearchFromInput(event.target);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
searchForms.forEach(form => {
|
||||||
|
form.addEventListener(
|
||||||
|
'submit',
|
||||||
|
event => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (event.stopImmediatePropagation) event.stopImmediatePropagation();
|
||||||
|
event.stopPropagation();
|
||||||
|
const searchInput = form.querySelector('[data-search-input]');
|
||||||
|
if (searchInput) updateSearchFromInput(searchInput, { blur: true });
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
searchInputs.forEach(input => {
|
||||||
|
let isComposing = false;
|
||||||
|
|
||||||
|
input.addEventListener('compositionstart', () => {
|
||||||
|
isComposing = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('blur', event => {
|
||||||
|
updateSearchFromInput(event.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('compositionend', event => {
|
||||||
|
isComposing = false;
|
||||||
|
updateSearchFromInput(event.target);
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('keydown', event => {
|
||||||
|
if (event.key !== 'Enter') return;
|
||||||
|
if (isComposing || event.isComposing || event.keyCode === 229) return;
|
||||||
|
event.preventDefault();
|
||||||
|
updateSearchFromInput(event.target, { blur: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
input.addEventListener('keyup', event => {
|
||||||
|
if (isComposing || event.isComposing || event.keyCode === 229) return;
|
||||||
|
updateSearchFromInput(event.target, { blur: event.key === 'Enter' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
resetSearchButtons.forEach(button => {
|
||||||
|
function clearSearch(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
updateSearchText('');
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener('pointerdown', clearSearch);
|
||||||
|
button.addEventListener('touchstart', clearSearch);
|
||||||
|
button.addEventListener('click', clearSearch);
|
||||||
|
});
|
||||||
|
|
||||||
// 언어 필터
|
// 언어 필터
|
||||||
document.querySelectorAll('input[name="language-filter"]').forEach(checkbox => {
|
document.querySelectorAll('input[name="language-filter"]').forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', () => {
|
checkbox.addEventListener('change', () => {
|
||||||
@@ -695,7 +997,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 상태 필터
|
// 판매 상태 필터
|
||||||
document.querySelectorAll('input[name="status-filter"]').forEach(checkbox => {
|
document.querySelectorAll('input[name="status-filter"]').forEach(checkbox => {
|
||||||
checkbox.addEventListener('change', () => {
|
checkbox.addEventListener('change', () => {
|
||||||
updateFilters('status');
|
updateFilters('status');
|
||||||
@@ -729,48 +1031,53 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 필터링 함수
|
// 필터링 함수
|
||||||
function filterGames(games) {
|
function filterGames(games) {
|
||||||
return games.filter(game => {
|
return games.filter(game => {
|
||||||
|
if (filterState.searchText) {
|
||||||
|
const searchableText = [
|
||||||
|
game.title,
|
||||||
|
game.koTitle,
|
||||||
|
game.maker,
|
||||||
|
game.tags,
|
||||||
|
game.country,
|
||||||
|
game.status,
|
||||||
|
String(game.sale?.suggestedPrice || ''),
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' ')
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
if (!searchableText.includes(filterState.searchText)) return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 언어 필터
|
// 언어 필터
|
||||||
if (filterState.filters.language.length > 0) {
|
if (filterState.filters.language.length > 0) {
|
||||||
const hasKorean = game.language.includes('韓国語');
|
const hasKorean = game.language?.includes('韓国語');
|
||||||
const hasKoreanFilter = filterState.filters.language.includes('koreanSupport');
|
const hasKoreanFilter = filterState.filters.language.includes('koreanSupport');
|
||||||
const hasNotSupportedFilter = filterState.filters.language.includes('koreanNotSupport');
|
const hasNotSupportedFilter = filterState.filters.language.includes('koreanNotSupport');
|
||||||
|
|
||||||
// 한국어 지원과 미지원이 모두 체크된 경우 모든 게임 표시
|
|
||||||
if (hasKoreanFilter && hasNotSupportedFilter) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 한국어 지원만 체크된 경우 한국어 지원 게임만 표시
|
// 한국어 지원만 체크된 경우 한국어 지원 게임만 표시
|
||||||
if (hasKoreanFilter && !hasNotSupportedFilter) {
|
if (hasKoreanFilter && !hasNotSupportedFilter) {
|
||||||
return hasKorean;
|
if (!hasKorean) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 한국어 미지원만 체크된 경우 한국어 미지원 게임만 표시
|
// 한국어 미지원만 체크된 경우 한국어 미지원 게임만 표시
|
||||||
if (!hasKoreanFilter && hasNotSupportedFilter) {
|
if (!hasKoreanFilter && hasNotSupportedFilter) {
|
||||||
return !hasKorean;
|
if (hasKorean) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 아무것도 체크되지 않은 경우 필터링하지 않음
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 상태 필터
|
if (
|
||||||
if (filterState.filters.status.length > 0) {
|
!SHOW_SOLD_BY_DEFAULT &&
|
||||||
const hasExtensionFilter = filterState.filters.status.includes('extension');
|
filterState.filters.status.length === 0 &&
|
||||||
const hasOtherStatusFilters =
|
game.status === 'sold'
|
||||||
filterState.filters.status.filter(status => status !== 'extension').length > 0;
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// extension 필터가 체크된 경우
|
if (
|
||||||
if (hasExtensionFilter) {
|
filterState.filters.status.length > 0 &&
|
||||||
// extension 값이 null이 아닌 게임만 표시
|
!filterState.filters.status.includes(game.status)
|
||||||
if (game.extension === null) return false;
|
) {
|
||||||
}
|
return false;
|
||||||
|
|
||||||
// 다른 상태 필터가 체크된 경우
|
|
||||||
if (hasOtherStatusFilters) {
|
|
||||||
const otherStatuses = filterState.filters.status.filter(status => status !== 'extension');
|
|
||||||
if (!otherStatuses.includes(game.status)) return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 국가 필터
|
// 국가 필터
|
||||||
@@ -793,43 +1100,39 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
// 정렬 함수
|
// 정렬 함수
|
||||||
function sortGames(games) {
|
function sortGames(games) {
|
||||||
switch (filterState.sortBy) {
|
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':
|
case 'sortByDateDesc':
|
||||||
return [...games].sort((a, b) => {
|
return [...games].sort((a, b) => {
|
||||||
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||||
const bDate = new Date(b.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);
|
const dateDiff = new Date(bDate) - new Date(aDate);
|
||||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
return dateDiff === 0 ? compareTitles(a, b) : dateDiff;
|
||||||
});
|
});
|
||||||
case 'sortByDate':
|
case 'sortByDate':
|
||||||
return [...games].sort((a, b) => {
|
return [...games].sort((a, b) => {
|
||||||
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
const aDate = new Date(a.release.replace(/年|月/g, '/').replace(/日/g, '')).toUTCString();
|
||||||
const bDate = new Date(b.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);
|
const dateDiff = new Date(aDate) - new Date(bDate);
|
||||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
return dateDiff === 0 ? compareTitles(a, b) : dateDiff;
|
||||||
});
|
});
|
||||||
case 'sortByPurchaseDateDesc':
|
case 'sortByPriceDesc':
|
||||||
return [...games].sort((a, b) => {
|
return [...games].sort((a, b) => {
|
||||||
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
|
const aPrice = getSuggestedPriceValue(a);
|
||||||
return !a.purchaseInformation?.date ? 1 : -1;
|
const bPrice = getSuggestedPriceValue(b);
|
||||||
}
|
if (aPrice === null && bPrice === null) return compareTitles(a, b);
|
||||||
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
if (aPrice === null) return 1;
|
||||||
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
if (bPrice === null) return -1;
|
||||||
const dateDiff = new Date(bDate) - new Date(aDate);
|
const priceDiff = bPrice - aPrice;
|
||||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
return priceDiff === 0 ? compareTitles(a, b) : priceDiff;
|
||||||
});
|
});
|
||||||
case 'sortByPurchaseDate':
|
case 'sortByPrice':
|
||||||
return [...games].sort((a, b) => {
|
return [...games].sort((a, b) => {
|
||||||
if (!a.purchaseInformation?.date || !b.purchaseInformation?.date) {
|
const aPrice = getSuggestedPriceValue(a);
|
||||||
return !a.purchaseInformation?.date ? 1 : -1;
|
const bPrice = getSuggestedPriceValue(b);
|
||||||
}
|
if (aPrice === null && bPrice === null) return compareTitles(a, b);
|
||||||
const aDate = new Date(a.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
if (aPrice === null) return 1;
|
||||||
const bDate = new Date(b.purchaseInformation.date.replace(/\./g, '/')).toUTCString();
|
if (bPrice === null) return -1;
|
||||||
const dateDiff = new Date(aDate) - new Date(bDate);
|
const priceDiff = aPrice - bPrice;
|
||||||
return dateDiff === 0 ? b.no - a.no : dateDiff;
|
return priceDiff === 0 ? compareTitles(a, b) : priceDiff;
|
||||||
});
|
});
|
||||||
case 'sortByRandom':
|
case 'sortByRandom':
|
||||||
return [...games].sort(() => Math.random() - 0.5);
|
return [...games].sort(() => Math.random() - 0.5);
|
||||||
@@ -838,6 +1141,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareTitles(a, b) {
|
||||||
|
return a.formattedTitle.localeCompare(b.formattedTitle, filterState.language === 'ko' ? 'ko' : 'ja');
|
||||||
|
}
|
||||||
|
|
||||||
// 정렬 UI 초기화
|
// 정렬 UI 초기화
|
||||||
function setupSortUI() {
|
function setupSortUI() {
|
||||||
const sortButton = document.getElementById('sort-button');
|
const sortButton = document.getElementById('sort-button');
|
||||||
|
|||||||
@@ -15,17 +15,12 @@
|
|||||||
--color-yellow-800: oklch(47.6% 0.114 61.907);
|
--color-yellow-800: oklch(47.6% 0.114 61.907);
|
||||||
--color-green-100: oklch(96.2% 0.044 156.743);
|
--color-green-100: oklch(96.2% 0.044 156.743);
|
||||||
--color-green-800: oklch(44.8% 0.119 151.328);
|
--color-green-800: oklch(44.8% 0.119 151.328);
|
||||||
--color-teal-100: oklch(95.3% 0.051 180.801);
|
|
||||||
--color-teal-800: oklch(43.7% 0.078 188.216);
|
|
||||||
--color-blue-100: oklch(93.2% 0.032 255.585);
|
--color-blue-100: oklch(93.2% 0.032 255.585);
|
||||||
--color-blue-800: oklch(42.4% 0.199 265.638);
|
--color-blue-800: oklch(42.4% 0.199 265.638);
|
||||||
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
||||||
--color-indigo-400: oklch(67.3% 0.182 276.935);
|
|
||||||
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
--color-indigo-500: oklch(58.5% 0.233 277.117);
|
||||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
||||||
--color-indigo-900: oklch(35.9% 0.144 278.697);
|
--color-indigo-900: oklch(35.9% 0.144 278.697);
|
||||||
--color-purple-100: oklch(94.6% 0.033 307.174);
|
|
||||||
--color-purple-800: oklch(43.8% 0.218 303.724);
|
|
||||||
--color-slate-50: oklch(98.4% 0.003 247.858);
|
--color-slate-50: oklch(98.4% 0.003 247.858);
|
||||||
--color-gray-50: oklch(98.5% 0.002 247.839);
|
--color-gray-50: oklch(98.5% 0.002 247.839);
|
||||||
--color-gray-100: oklch(96.7% 0.003 264.542);
|
--color-gray-100: oklch(96.7% 0.003 264.542);
|
||||||
@@ -33,14 +28,12 @@
|
|||||||
--color-gray-300: oklch(87.2% 0.01 258.338);
|
--color-gray-300: oklch(87.2% 0.01 258.338);
|
||||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||||
--color-gray-500: oklch(55.1% 0.027 264.364);
|
--color-gray-500: oklch(55.1% 0.027 264.364);
|
||||||
--color-gray-600: oklch(44.6% 0.03 256.802);
|
|
||||||
--color-gray-700: oklch(37.3% 0.034 259.733);
|
--color-gray-700: oklch(37.3% 0.034 259.733);
|
||||||
--color-gray-800: oklch(27.8% 0.033 256.848);
|
|
||||||
--color-gray-900: oklch(21% 0.034 264.665);
|
--color-gray-900: oklch(21% 0.034 264.665);
|
||||||
--color-black: #000;
|
--color-black: #000;
|
||||||
--color-white: #fff;
|
--color-white: #fff;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
--container-xs: 20rem;
|
--container-lg: 32rem;
|
||||||
--container-2xl: 42rem;
|
--container-2xl: 42rem;
|
||||||
--container-4xl: 56rem;
|
--container-4xl: 56rem;
|
||||||
--container-7xl: 80rem;
|
--container-7xl: 80rem;
|
||||||
@@ -61,9 +54,6 @@
|
|||||||
--radius-md: 0.375rem;
|
--radius-md: 0.375rem;
|
||||||
--radius-lg: 0.5rem;
|
--radius-lg: 0.5rem;
|
||||||
--radius-xl: 0.75rem;
|
--radius-xl: 0.75rem;
|
||||||
--ease-in: cubic-bezier(0.4, 0, 1, 1);
|
|
||||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
--default-transition-duration: 150ms;
|
--default-transition-duration: 150ms;
|
||||||
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
--default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--default-font-family: var(--font-sans);
|
--default-font-family: var(--font-sans);
|
||||||
@@ -240,24 +230,6 @@
|
|||||||
.row-start-1 {
|
.row-start-1 {
|
||||||
grid-row-start: 1;
|
grid-row-start: 1;
|
||||||
}
|
}
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
@media (width >= 40rem) {
|
|
||||||
max-width: 40rem;
|
|
||||||
}
|
|
||||||
@media (width >= 48rem) {
|
|
||||||
max-width: 48rem;
|
|
||||||
}
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
max-width: 64rem;
|
|
||||||
}
|
|
||||||
@media (width >= 80rem) {
|
|
||||||
max-width: 80rem;
|
|
||||||
}
|
|
||||||
@media (width >= 96rem) {
|
|
||||||
max-width: 96rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.-mx-4 {
|
.-mx-4 {
|
||||||
margin-inline: calc(var(--spacing) * -4);
|
margin-inline: calc(var(--spacing) * -4);
|
||||||
}
|
}
|
||||||
@@ -276,6 +248,9 @@
|
|||||||
.mt-2 {
|
.mt-2 {
|
||||||
margin-top: calc(var(--spacing) * 2);
|
margin-top: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
|
.mt-3 {
|
||||||
|
margin-top: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: calc(var(--spacing) * 4);
|
margin-top: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -288,9 +263,6 @@
|
|||||||
.mt-16 {
|
.mt-16 {
|
||||||
margin-top: calc(var(--spacing) * 16);
|
margin-top: calc(var(--spacing) * 16);
|
||||||
}
|
}
|
||||||
.mr-1 {
|
|
||||||
margin-right: calc(var(--spacing) * 1);
|
|
||||||
}
|
|
||||||
.mb-4 {
|
.mb-4 {
|
||||||
margin-bottom: calc(var(--spacing) * 4);
|
margin-bottom: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -303,6 +275,12 @@
|
|||||||
.ml-4 {
|
.ml-4 {
|
||||||
margin-left: calc(var(--spacing) * 4);
|
margin-left: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
|
.line-clamp-2 {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
}
|
||||||
.block {
|
.block {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -349,9 +327,6 @@
|
|||||||
.max-h-96 {
|
.max-h-96 {
|
||||||
max-height: calc(var(--spacing) * 96);
|
max-height: calc(var(--spacing) * 96);
|
||||||
}
|
}
|
||||||
.w-3\/12 {
|
|
||||||
width: calc(3/12 * 100%);
|
|
||||||
}
|
|
||||||
.w-4 {
|
.w-4 {
|
||||||
width: calc(var(--spacing) * 4);
|
width: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -361,12 +336,18 @@
|
|||||||
.w-10 {
|
.w-10 {
|
||||||
width: calc(var(--spacing) * 10);
|
width: calc(var(--spacing) * 10);
|
||||||
}
|
}
|
||||||
.w-24 {
|
.w-28 {
|
||||||
width: calc(var(--spacing) * 24);
|
width: calc(var(--spacing) * 28);
|
||||||
|
}
|
||||||
|
.w-32 {
|
||||||
|
width: calc(var(--spacing) * 32);
|
||||||
}
|
}
|
||||||
.w-40 {
|
.w-40 {
|
||||||
width: calc(var(--spacing) * 40);
|
width: calc(var(--spacing) * 40);
|
||||||
}
|
}
|
||||||
|
.w-56 {
|
||||||
|
width: calc(var(--spacing) * 56);
|
||||||
|
}
|
||||||
.w-\[calc\(100\%-3rem\)\] {
|
.w-\[calc\(100\%-3rem\)\] {
|
||||||
width: calc(100% - 3rem);
|
width: calc(100% - 3rem);
|
||||||
}
|
}
|
||||||
@@ -379,6 +360,9 @@
|
|||||||
.max-w-7xl {
|
.max-w-7xl {
|
||||||
max-width: var(--container-7xl);
|
max-width: var(--container-7xl);
|
||||||
}
|
}
|
||||||
|
.max-w-lg {
|
||||||
|
max-width: var(--container-lg);
|
||||||
|
}
|
||||||
.min-w-0 {
|
.min-w-0 {
|
||||||
min-width: calc(var(--spacing) * 0);
|
min-width: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
@@ -391,21 +375,12 @@
|
|||||||
.flex-shrink-0 {
|
.flex-shrink-0 {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.flex-grow {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.rotate-180 {
|
|
||||||
rotate: 180deg;
|
|
||||||
}
|
|
||||||
.transform {
|
.transform {
|
||||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||||
}
|
}
|
||||||
.cursor-pointer {
|
.cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.resize {
|
|
||||||
resize: both;
|
|
||||||
}
|
|
||||||
.grid-cols-1 {
|
.grid-cols-1 {
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
grid-template-columns: repeat(1, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
@@ -421,6 +396,9 @@
|
|||||||
.items-center {
|
.items-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
.items-start {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
.justify-between {
|
.justify-between {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
@@ -444,6 +422,9 @@
|
|||||||
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-x-3 {
|
||||||
|
column-gap: calc(var(--spacing) * 3);
|
||||||
|
}
|
||||||
.gap-x-4 {
|
.gap-x-4 {
|
||||||
column-gap: calc(var(--spacing) * 4);
|
column-gap: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
@@ -460,6 +441,9 @@
|
|||||||
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.gap-y-1 {
|
||||||
|
row-gap: calc(var(--spacing) * 1);
|
||||||
|
}
|
||||||
.gap-y-2 {
|
.gap-y-2 {
|
||||||
row-gap: calc(var(--spacing) * 2);
|
row-gap: calc(var(--spacing) * 2);
|
||||||
}
|
}
|
||||||
@@ -553,27 +537,18 @@
|
|||||||
.bg-gray-50 {
|
.bg-gray-50 {
|
||||||
background-color: var(--color-gray-50);
|
background-color: var(--color-gray-50);
|
||||||
}
|
}
|
||||||
.bg-gray-100 {
|
|
||||||
background-color: var(--color-gray-100);
|
|
||||||
}
|
|
||||||
.bg-green-100 {
|
.bg-green-100 {
|
||||||
background-color: var(--color-green-100);
|
background-color: var(--color-green-100);
|
||||||
}
|
}
|
||||||
.bg-indigo-600 {
|
.bg-indigo-600 {
|
||||||
background-color: var(--color-indigo-600);
|
background-color: var(--color-indigo-600);
|
||||||
}
|
}
|
||||||
.bg-purple-100 {
|
|
||||||
background-color: var(--color-purple-100);
|
|
||||||
}
|
|
||||||
.bg-red-100 {
|
.bg-red-100 {
|
||||||
background-color: var(--color-red-100);
|
background-color: var(--color-red-100);
|
||||||
}
|
}
|
||||||
.bg-slate-50 {
|
.bg-slate-50 {
|
||||||
background-color: var(--color-slate-50);
|
background-color: var(--color-slate-50);
|
||||||
}
|
}
|
||||||
.bg-teal-100 {
|
|
||||||
background-color: var(--color-teal-100);
|
|
||||||
}
|
|
||||||
.bg-white {
|
.bg-white {
|
||||||
background-color: var(--color-white);
|
background-color: var(--color-white);
|
||||||
}
|
}
|
||||||
@@ -606,9 +581,6 @@
|
|||||||
.px-4 {
|
.px-4 {
|
||||||
padding-inline: calc(var(--spacing) * 4);
|
padding-inline: calc(var(--spacing) * 4);
|
||||||
}
|
}
|
||||||
.py-0\.5 {
|
|
||||||
padding-block: calc(var(--spacing) * 0.5);
|
|
||||||
}
|
|
||||||
.py-1 {
|
.py-1 {
|
||||||
padding-block: calc(var(--spacing) * 1);
|
padding-block: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
@@ -726,12 +698,12 @@
|
|||||||
--tw-tracking: var(--tracking-tight);
|
--tw-tracking: var(--tracking-tight);
|
||||||
letter-spacing: var(--tracking-tight);
|
letter-spacing: var(--tracking-tight);
|
||||||
}
|
}
|
||||||
|
.whitespace-nowrap {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.text-blue-800 {
|
.text-blue-800 {
|
||||||
color: var(--color-blue-800);
|
color: var(--color-blue-800);
|
||||||
}
|
}
|
||||||
.text-gray-300 {
|
|
||||||
color: var(--color-gray-300);
|
|
||||||
}
|
|
||||||
.text-gray-400 {
|
.text-gray-400 {
|
||||||
color: var(--color-gray-400);
|
color: var(--color-gray-400);
|
||||||
}
|
}
|
||||||
@@ -750,27 +722,18 @@
|
|||||||
.text-indigo-600 {
|
.text-indigo-600 {
|
||||||
color: var(--color-indigo-600);
|
color: var(--color-indigo-600);
|
||||||
}
|
}
|
||||||
.text-purple-800 {
|
|
||||||
color: var(--color-purple-800);
|
|
||||||
}
|
|
||||||
.text-red-600 {
|
.text-red-600 {
|
||||||
color: var(--color-red-600);
|
color: var(--color-red-600);
|
||||||
}
|
}
|
||||||
.text-red-800 {
|
.text-red-800 {
|
||||||
color: var(--color-red-800);
|
color: var(--color-red-800);
|
||||||
}
|
}
|
||||||
.text-teal-800 {
|
|
||||||
color: var(--color-teal-800);
|
|
||||||
}
|
|
||||||
.text-white {
|
.text-white {
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
}
|
}
|
||||||
.text-yellow-800 {
|
.text-yellow-800 {
|
||||||
color: var(--color-yellow-800);
|
color: var(--color-yellow-800);
|
||||||
}
|
}
|
||||||
.line-through {
|
|
||||||
text-decoration-line: line-through;
|
|
||||||
}
|
|
||||||
.shadow {
|
.shadow {
|
||||||
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
||||||
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
||||||
@@ -816,11 +779,6 @@
|
|||||||
.filter {
|
.filter {
|
||||||
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);
|
||||||
}
|
}
|
||||||
.transition {
|
|
||||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;
|
|
||||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
||||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
|
||||||
}
|
|
||||||
.transition-transform {
|
.transition-transform {
|
||||||
transition-property: transform, translate, scale, rotate;
|
transition-property: transform, translate, scale, rotate;
|
||||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||||
@@ -970,6 +928,11 @@
|
|||||||
margin-top: calc(var(--spacing) * 0);
|
margin-top: calc(var(--spacing) * 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.sm\:hidden {
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
.sm\:table-cell {
|
.sm\:table-cell {
|
||||||
@media (width >= 40rem) {
|
@media (width >= 40rem) {
|
||||||
display: table-cell;
|
display: table-cell;
|
||||||
@@ -1069,11 +1032,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.lg\:table-cell {
|
|
||||||
@media (width >= 64rem) {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lg\:max-h-none {
|
.lg\:max-h-none {
|
||||||
@media (width >= 64rem) {
|
@media (width >= 64rem) {
|
||||||
max-height: none;
|
max-height: none;
|
||||||
|
|||||||
Reference in New Issue
Block a user