import products from '/data.js'; const ITEMS_PER_PAGE = 20; let currentPage = 1; let activeCategories = new Set(['All']); let visibleProducts = products; let searchKeyword = ''; const VISIBILITY_CONFIG = { showUnlisted: false, // ๐Ÿ”ฅ ๋ฏธํŒ๋งค ๋…ธ์ถœ ์—ฌ๋ถ€ showSold: true, // ๐Ÿ”ฅ ํŒ๋งค์™„๋ฃŒ ๋…ธ์ถœ ์—ฌ๋ถ€ }; const STATUS_META = { ๋ฏธํŒ๋งค: { selectable: false, defaultVisible: false, soldOut: false, }, ํŒ๋งค์˜ˆ์ •: { selectable: true, defaultVisible: true, soldOut: false, }, ํŒ๋งค์ค‘: { selectable: true, defaultVisible: true, soldOut: false, }, ํŒ๋งค์™„๋ฃŒ: { selectable: true, defaultVisible: false, soldOut: true, }, }; const STATUS_FILTERS = [ { key: 'ํŒ๋งค์ค‘', label: 'ํŒ๋งค์ค‘', defaultActive: true, visible: true, }, { key: 'ํŒ๋งค์˜ˆ์ •', label: 'ํŒ๋งค ์˜ˆ์ •', defaultActive: true, visible: true, }, { key: '๋ฏธํŒ๋งค', label: '๋ฏธํŒ๋งค', defaultActive: false, visible: VISIBILITY_CONFIG.showUnlisted, }, { key: 'ํŒ๋งค์™„๋ฃŒ', label: 'ํŒ๋งค์™„๋ฃŒ', defaultActive: false, visible: VISIBILITY_CONFIG.showSold, }, ]; const STATUS_ORDER = { ํŒ๋งค์ค‘: 0, ํŒ๋งค์˜ˆ์ •: 1, ๋ฏธํŒ๋งค: 2, ํŒ๋งค์™„๋ฃŒ: 3, // ๐Ÿ”ฅ ํ•ญ์ƒ ๋งจ ๋’ค }; const STATUS_COLOR = { ํŒ๋งค์ค‘: 'bg-primary/10 text-primary border-primary/30', ํŒ๋งค์˜ˆ์ •: 'bg-amber-400/10 text-amber-600 border-amber-400/30', ํŒ๋งค์™„๋ฃŒ: 'bg-slate-400/10 text-slate-500 border-slate-400/30', ๋ฏธํŒ๋งค: 'bg-slate-200/10 text-slate-400 border-slate-300/30', }; let activeStatuses = new Set( Object.entries(STATUS_META) .filter(([_, meta]) => meta.defaultVisible) .map(([status]) => status), ); function getStatusChipClass(status, isActive) { const base = STATUS_COLOR[status] ?? ''; if (isActive) { return ` ${base} opacity-100 shadow-sm `; } // ๐Ÿ”ฅ ๋น„ํ™œ์„ฑ return ` bg-slate-50 text-slate-400 border-slate-200 opacity-30 grayscale hover:opacity-50 `; } function renderStatusChips() { const container = document.getElementById('status-chips'); if (!container) return; container.innerHTML = ''; STATUS_FILTERS.filter((f) => f.visible).forEach(({ key, label }) => { const isActive = activeStatuses.has(key); const chip = document.createElement('button'); chip.className = ` status-chip px-4 py-2 rounded-full text-sm font-medium transition-all duration-200 border ${getStatusChipClass(key, isActive)} `; chip.textContent = label; chip.onclick = () => toggleStatusFilter(key); container.appendChild(chip); }); } function toggleStatusFilter(status) { if (activeStatuses.has(status)) { activeStatuses.delete(status); } else { activeStatuses.add(status); } // ์ตœ์†Œ 1๊ฐœ๋Š” ์œ ์ง€ if (activeStatuses.size === 0) { STATUS_FILTERS.filter((f) => f.defaultActive).forEach((f) => activeStatuses.add(f.key)); } applyFilters(); renderStatusChips(); } const searchInput = document.getElementById('search-input'); if (searchInput) { searchInput.addEventListener('input', (e) => { searchKeyword = e.target.value.trim().toLowerCase(); applyFilters(); }); } function applyFilters() { currentPage = 1; visibleProducts = products .filter((product) => { // ๐Ÿ”’ ๋ฏธํŒ๋งค ๊ฐ•์ œ ์ฐจ๋‹จ if (product.status === '๋ฏธํŒ๋งค' && !VISIBILITY_CONFIG.showUnlisted) { return false; } // ๐Ÿ”’ ํŒ๋งค์™„๋ฃŒ ๊ธฐ๋ณธ ์ˆจ๊น€ if (product.status === 'ํŒ๋งค์™„๋ฃŒ' && !VISIBILITY_CONFIG.showSold) { return false; } const statusMatch = activeStatuses.has(product.status); const categoryMatch = activeCategories.has('All') || activeCategories.has(product.category); const searchMatch = searchKeyword === '' || product.title.toLowerCase().includes(searchKeyword); return statusMatch && categoryMatch && searchMatch; }) // ๐Ÿ”ฅ ์—ฌ๊ธฐ์„œ ์ •๋ ฌ .sort((a, b) => { const aOrder = STATUS_ORDER[a.status] ?? 999; const bOrder = STATUS_ORDER[b.status] ?? 999; return aOrder - bOrder; }); renderProducts(currentPage); } /** * 1. ์ƒํ’ˆ ๋ชฉ๋ก ๋ Œ๋”๋ง */ export function renderProducts(page) { const grid = document.getElementById('product-grid'); if (!grid) return; grid.innerHTML = ''; const startIndex = (page - 1) * ITEMS_PER_PAGE; const pagedProducts = visibleProducts.slice(startIndex, startIndex + ITEMS_PER_PAGE); pagedProducts.forEach((product) => { const isSold = STATUS_META[product.status]?.soldOut === true; const cardHtml = `
${product.status}

${product.title}

${product.currency}${product.price.toLocaleString()}

${product.description}

`; grid.insertAdjacentHTML('beforeend', cardHtml); }); renderPagination(); } /** * 2. ๋ชจ๋‹ฌ ์—ด๊ธฐ ๋ฐ ๋ฐ์ดํ„ฐ ์ฑ„์šฐ๊ธฐ */ window.openModal = (id) => { const product = products.find((p) => p.id === id); if (!product) return; const modal = document.getElementById('product-modal'); const images = product.images; // ๋ฌดํ•œ ๋ฃจํ”„๋ฅผ ์œ„ํ•ด ์ฒ˜์Œ๊ณผ ๋์— ํด๋ก  ์ถ”๊ฐ€ [๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€, ...์›๋ณธ ์ด๋ฏธ์ง€..., ์ฒซ ์ด๋ฏธ์ง€] const loopImages = [images[images.length - 1], ...images, images[0]]; const mainImagesHtml = loopImages .map( (img) => `
`, ) .join(''); // 2. ์‚ฌ์ด๋“œ ์ธ๋„ค์ผ ๋™์  ์ƒ์„ฑ const thumbnailsHtml = product.images .map( (img, idx) => ` `, ) .join(''); // 3. ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋„ํŠธ ๋™์  ์ƒ์„ฑ const dotsHtml = product.images .map( (_, idx) => ` `, ) .join(''); const customTagElement = document.getElementById('modal-custom-tag'); const tagText = product.customTag; if (tagText && tagText.trim() !== '') { customTagElement.textContent = tagText; customTagElement.classList.remove('hidden'); // ํ‚ค์›Œ๋“œ๋ณ„ ์ƒ‰์ƒ ์Šคํƒ€์ผ ์ •์˜ const tagStyles = { ์™„์ „์ƒ์‚ฐํ•œ์ •ํŒ: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', ํŠน์ „ํฌํ•จ: 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400', ๋ฏธ๊ฐœ๋ด‰: 'bg-orange-100 text-orange-700 dark:bg-orange-900/30 dark:text-orange-400', ๋ฌด๋ฃŒ๋ฐฐ์†ก: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', }; // ๊ธฐ๋ณธ ์Šคํƒ€์ผ (์ •์˜๋˜์ง€ ์•Š์€ ํ…์ŠคํŠธ์ผ ๊ฒฝ์šฐ) const defaultStyle = 'bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400'; // ๊ธฐ์กด ์Šคํƒ€์ผ ํด๋ž˜์Šค ์ดˆ๊ธฐํ™” ํ›„ ์ ์šฉ customTagElement.className = `px-2.5 py-1 rounded-lg text-xs font-bold uppercase tracking-wider ${tagStyles[tagText] || defaultStyle}`; } else { customTagElement.classList.add('hidden'); } const modalCategory = document.getElementById('modal-category'); if (modalCategory) { modalCategory.textContent = product.category; } // 2. ์ƒํƒœ๊ฐ’ ์—…๋ฐ์ดํŠธ (์ƒํƒœ์— ๋”ฐ๋ฅธ ๋””์ž์ธ ๋ณ€๊ฒฝ ํฌํ•จ) const modalStatus = document.getElementById('modal-status'); if (modalStatus) { modalStatus.textContent = product.status; // ์ƒํƒœ๋ณ„ ์Šคํƒ€์ผ ์ •์˜ (๋ฐฐ๊ฒฝ์ƒ‰, ๊ธ€์ž์ƒ‰) const statusStyles = { ํŒ๋งค์ค‘: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', ํŒ๋งค์˜ˆ์ •: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', ํŒ๋งค์™„๋ฃŒ: 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400', ๋ฏธํŒ๋งค: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400', }; // ๊ธฐ์กด ํด๋ž˜์Šค ์ œ๊ฑฐ ํ›„ ํ•ด๋‹น ์ƒํƒœ์˜ ์Šคํƒ€์ผ ์ถ”๊ฐ€ modalStatus.className = 'px-2.5 py-1 rounded-lg text-xs font-bold uppercase tracking-wider ' + (statusStyles[product.status] || statusStyles['๋ฏธํŒ๋งค']); } // HTML ์ฃผ์ž… document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml; document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml; document.getElementById('modal-dots').innerHTML = dotsHtml; // 1. ์ œํ’ˆ ์ƒํƒœ (specs.condition) ์—…๋ฐ์ดํŠธ const conditionText = product.specs?.condition; const isVerified = product.specs?.isVerified; const conditionRow = document.getElementById('modal-condition-row'); const conditionValue = document.getElementById('modal-condition'); const verifiedIcon = document.getElementById('modal-verified-icon'); // 1. ๊ทธ๋ ˆ์ด๋“œ(condition) ๊ฐ’์ด ์žˆ๋Š”์ง€ ์ฒดํฌํ•˜์—ฌ ๋ผ์ธ ๋…ธ์ถœ ๊ฒฐ์ • if (conditionText && conditionText.trim() !== '') { conditionValue.textContent = conditionText; conditionRow.classList.remove('hidden'); conditionRow.classList.add('flex'); // 2. ์ธ์ฆ ์—ฌ๋ถ€์— ๋”ฐ๋ผ SVG ์•„์ด์ฝ˜ ๋…ธ์ถœ ๊ฒฐ์ • if (isVerified) { verifiedIcon.classList.remove('hidden'); } else { verifiedIcon.classList.add('hidden'); } } else { // ๊ฐ’์ด ์—†์œผ๋ฉด ๋ผ์ธ ์ „์ฒด ์ˆจ๊น€ conditionRow.classList.add('hidden'); conditionRow.classList.remove('flex'); } // 2. ์ œํ’ˆ ์„ค๋ช… (fullDescription) ์—…๋ฐ์ดํŠธ const modalDesc = document.getElementById('modal-desc'); if (modalDesc) { const descData = product.fullDescription; if (Array.isArray(descData)) { // ๋ฐฐ์—ด์„
ํƒœ๊ทธ๋กœ ํ•ฉ์ณ์„œ HTML๋กœ ์ฃผ์ž… modalDesc.innerHTML = descData.join('
'); } else { modalDesc.textContent = descData || ''; } } // ๊ตฌ๋งค์ผ์ž ์ฒ˜๋ฆฌ (๊ฒฝ๋กœ ์ˆ˜์ •: product.specs?.purchaseDate) const modalDate = document.getElementById('modal-date'); const modalDateRow = document.getElementById('modal-date-row'); // specs ๊ฐ์ฒด ์•ˆ์˜ purchaseDate๋ฅผ ๊ฐ€์ ธ์˜ด const pDate = product.specs?.purchaseDate; if (pDate && pDate.trim() !== '' && pDate !== 'null') { modalDate.textContent = pDate; modalDateRow.classList.remove('hidden'); modalDateRow.classList.add('flex'); } else { modalDateRow.classList.add('hidden'); modalDateRow.classList.remove('flex'); } // ํ…์ŠคํŠธ ์ •๋ณด ์ฃผ์ž… (ID๋“ค ๋งž์ถฐ์ฃผ์„ธ์š”) document.getElementById('modal-title').textContent = product.title; document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`; // ... ๋‚˜๋จธ์ง€ ์ •๋ณด ์ฃผ์ž… modal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; // ๋“œ๋ž˜๊ทธ ๊ธฐ๋Šฅ ๋‹ค์‹œ ์—ฐ๊ฒฐ const container = document.getElementById('modal-main-carousel-container'); const carousel = document.getElementById('modal-main-carousel'); carousel.innerHTML = mainImagesHtml; modal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; // ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ • (ํด๋ก ๋œ ๋งˆ์ง€๋ง‰ ์ด๋ฏธ์ง€ ๋‹ค์Œ์ธ '์ง„์งœ ์ฒซ ๋ฒˆ์งธ' ์ด๋ฏธ์ง€๋กœ ์ด๋™) // const initialIndex = 1; container.style.scrollBehavior = 'auto'; container.scrollLeft = container.clientWidth; // ๋“œ๋ž˜๊ทธ ๋ฐ ๋ฌดํ•œ ๋ฃจํ”„ ๊ฐ์‹œ ์‹œ์ž‘ initBetterCarousel(container, images.length); }; function initBetterCarousel(container, originalLength) { let isDragging = false; let startX = 0; let startScroll = 0; let startTime = 0; const width = () => container.clientWidth; container.addEventListener('mousedown', start); container.addEventListener('touchstart', start, { passive: true }); function start(e) { isDragging = true; startX = e.touches ? e.touches[0].pageX : e.pageX; startScroll = container.scrollLeft; startTime = Date.now(); } container.addEventListener('mousemove', move); container.addEventListener('touchmove', move, { passive: false }); function move(e) { if (!isDragging) return; const x = e.touches ? e.touches[0].pageX : e.pageX; container.scrollLeft = startScroll - (x - startX); } container.addEventListener('mouseup', end); container.addEventListener('mouseleave', end); container.addEventListener('touchend', end); function end(e) { if (!isDragging) return; isDragging = false; const delta = container.scrollLeft - startScroll; const elapsed = Date.now() - startTime; const direction = Math.abs(delta) > width() * 0.1 || elapsed < 200 ? (delta > 0 ? 1 : -1) : 0; let index = Math.round(startScroll / width()) + direction; container.style.scrollBehavior = 'smooth'; container.scrollTo({ left: index * width() }); // ๋ฌดํ•œ ๋ฃจํ”„ ๋ณด์ • setTimeout(() => { container.style.scrollBehavior = 'auto'; if (index === 0) { container.scrollLeft = width() * originalLength; } if (index === originalLength + 1) { container.scrollLeft = width(); } syncModalUI(originalLength); }, 300); } } /** * ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋“œ์™€ UI(Dots, Thumbs) ๋™๊ธฐํ™” */ function syncModalUI(originalLength) { const container = document.getElementById('modal-main-carousel-container'); const index = getRealIndex(container, originalLength); document.querySelectorAll('.modal-thumb-item').forEach((t, i) => { t.classList.toggle('border-primary', i === index); t.classList.toggle('opacity-100', i === index); t.classList.toggle('opacity-70', i !== index); }); document.querySelectorAll('.modal-dot-item').forEach((d, i) => { d.classList.toggle('bg-primary', i === index); d.classList.toggle('w-4', i === index); d.classList.toggle('bg-gray-300', i !== index); d.classList.toggle('w-2', i !== index); }); ensureThumbnailVisible(index); } /** * 3. ๋ชจ๋‹ฌ ๋‚ด ์ด๋ฏธ์ง€ ์Šคํฌ๋กค ๋ฐ UI ๋™๊ธฐํ™” */ window.scrollToImage = (index) => { const container = document.getElementById('modal-main-carousel-container'); if (!container) return; container.scrollTo({ left: container.clientWidth * (index + 1), // ๐Ÿ”ฅ ์ค‘์š” behavior: 'smooth', }); }; /** * 5. ๊ธฐํƒ€ (ํŽ˜์ด์ง€๋„ค์ด์…˜, ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ) */ function renderPagination() { const container = document.getElementById('pagination'); if (!container) return; const totalPages = Math.ceil(visibleProducts.length / ITEMS_PER_PAGE); let html = ``; for (let i = 1; i <= totalPages; i++) { html += ``; } html += ``; container.innerHTML = html; } window.changePage = (page) => { currentPage = page; renderProducts(currentPage); window.scrollTo({ top: 0, behavior: 'smooth' }); }; window.closeModal = () => { document.getElementById('product-modal').classList.add('hidden'); document.body.style.overflow = 'auto'; }; // ์ดˆ๊ธฐ ์‹คํ–‰ document.addEventListener('DOMContentLoaded', () => renderProducts(currentPage)); document.addEventListener('keydown', (e) => { if (e.key !== 'Escape') return; const modal = document.getElementById('product-modal'); if (!modal || modal.classList.contains('hidden')) return; closeModal(); }); const thumbnailContainer = document.getElementById('modal-thumbnails'); function ensureThumbnailVisible(index) { const container = document.getElementById('modal-thumbnails'); if (!container) return; const thumbs = container.querySelectorAll('.modal-thumb-item'); const active = thumbs[index]; if (!active) return; const cRect = container.getBoundingClientRect(); const tRect = active.getBoundingClientRect(); const isVisible = tRect.top >= cRect.top && tRect.bottom <= cRect.bottom; if (!isVisible) { active.scrollIntoView({ behavior: 'smooth', block: 'center', }); } } function getRealIndex(container, originalLength) { let rawIndex = Math.round(container.scrollLeft / container.clientWidth); let index = rawIndex - 1; // ํด๋ก  ๋ณด์ • if (index < 0) index = originalLength - 1; if (index >= originalLength) index = 0; return index; } // ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ function getCategories(products) { return ['All', ...new Set(products.map((p) => p.category))]; } function renderCategoryChips(products) { const container = document.getElementById('filter-chips'); if (!container) return; const categories = ['All', ...new Set(products.map((p) => p.category))]; container.innerHTML = ''; categories.forEach((cat) => { const isActive = activeCategories.has(cat); const chip = document.createElement('button'); chip.className = ` filter-chip px-4 py-2 rounded-full text-sm font-medium transition border ${isActive ? 'bg-primary text-white border-primary' : 'bg-slate-50 text-slate-600 border-slate-200'} `; chip.textContent = cat; chip.dataset.category = cat; chip.onclick = () => { toggleCategory(cat); }; container.appendChild(chip); }); } function toggleCategory(category) { if (category === 'All') { activeCategories.clear(); activeCategories.add('All'); } else { activeCategories.delete('All'); activeCategories.has(category) ? activeCategories.delete(category) : activeCategories.add(category); if (activeCategories.size === 0) { activeCategories.add('All'); } } renderCategoryChips(products); applyFilters(); } function bindCategoryFilter(products) { const chips = document.querySelectorAll('.filter-chip'); chips.forEach((chip) => { chip.addEventListener('click', () => { const category = chip.dataset.category; if (category === 'All') { activeCategories.clear(); activeCategories.add('All'); } else { activeCategories.delete('All'); if (activeCategories.has(category)) { activeCategories.delete(category); } else { activeCategories.add(category); } // ์•„๋ฌด ๊ฒƒ๋„ ์—†์œผ๋ฉด All๋กœ ๋ณต๊ท€ if (activeCategories.size === 0) { activeCategories.add('All'); } } applyFilters(); }); }); } // ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ renderCategoryChips(products); bindCategoryFilter(products); // updateChipUI(); // ์ƒํƒœ ํ•„ํ„ฐ (์ •์ฑ… ๊ธฐ๋ฐ˜) renderStatusChips(); // ๐Ÿ”ฅ ์ตœ์ดˆ ํ•„ํ„ฐ ์ ์šฉ (์ด๊ฒŒ ์ฒซ ๋ Œ๋”) applyFilters();