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), ); // openModal ํ•จ์ˆ˜ ๋‚ด๋ถ€์— ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ „์—ญ์œผ๋กœ ์„ค์ • const copyBtn = document.getElementById('copy-link-btn'); const copyBtnText = document.getElementById('copy-btn-text'); copyBtn.onclick = () => { // ํ˜„์žฌ ๋„๋ฉ”์ธ + ์ œํ’ˆ ID ์ฟผ๋ฆฌ ์กฐํ•ฉ const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`; navigator.clipboard.writeText(shareUrl).then(() => { copyBtnText.textContent = "๋งํฌ๊ฐ€ ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"; copyBtn.classList.replace('bg-slate-900', 'bg-green-600'); setTimeout(() => { copyBtnText.textContent = "์ƒํ’ˆ ๋งํฌ ๋ณต์‚ฌํ•˜๊ธฐ"; copyBtn.classList.replace('bg-green-600', 'bg-slate-900'); }, 2000); }); }; 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; // --- 1. ์ด๋ฏธ์ง€ ๋ฐ UI ์ดˆ๊ธฐํ™” ๋กœ์ง --- const loopImages = [images[images.length - 1], ...images, images[0]]; const mainImagesHtml = loopImages .map(img => `
`).join(''); const thumbnailsHtml = product.images .map((img, idx) => ` `).join(''); const dotsHtml = product.images .map((_, idx) => ` `).join(''); // --- 2. ๋ฐ์ดํ„ฐ ์ฃผ์ž… --- document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml; document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml; document.getElementById('modal-dots').innerHTML = dotsHtml; document.getElementById('modal-title').textContent = product.title; document.getElementById('modal-price').textContent = `${product.currency}${product.price.toLocaleString()}`; // ์นดํ…Œ๊ณ ๋ฆฌ ๋ฐ ์ƒํƒœ const modalCategory = document.getElementById('modal-category'); if (modalCategory) modalCategory.textContent = product.category; 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['๋ฏธํŒ๋งค']); } // ์ปค์Šคํ…€ ํƒœ๊ทธ const customTagElement = document.getElementById('modal-custom-tag'); if (product.customTag?.trim()) { customTagElement.textContent = product.customTag; customTagElement.classList.remove('hidden'); } else { customTagElement.classList.add('hidden'); } // ์ƒ์„ธ ์„ค๋ช… const modalDesc = document.getElementById('modal-desc'); if (modalDesc) { modalDesc.innerHTML = Array.isArray(product.fullDescription) ? product.fullDescription.join('
') : (product.fullDescription || ''); } // --- 3. [ํ•ต์‹ฌ] ๋งํฌ ๋ณต์‚ฌ ๋ฒ„ํŠผ ์ด๋ฒคํŠธ ๋ฐ”์ธ๋”ฉ --- const copyBtn = document.getElementById('copy-link-btn'); const copyBtnText = document.getElementById('copy-btn-text'); if (copyBtn) { copyBtn.onclick = () => { const shareUrl = `${window.location.origin}${window.location.pathname}?id=${product.id}`; navigator.clipboard.writeText(shareUrl).then(() => { if (copyBtnText) copyBtnText.textContent = "๋งํฌ๊ฐ€ ๋ณต์‚ฌ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!"; copyBtn.classList.add('!bg-green-600'); setTimeout(() => { if (copyBtnText) copyBtnText.textContent = "์ƒํ’ˆ ๋งํฌ ๋ณต์‚ฌํ•˜๊ธฐ"; copyBtn.classList.remove('!bg-green-600'); }, 2000); }); }; } // --- 4. ๋ชจ๋‹ฌ ํ™œ์„ฑํ™” ๋ฐ ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ • --- modal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; const container = document.getElementById('modal-main-carousel-container'); container.style.scrollBehavior = 'auto'; container.scrollLeft = container.clientWidth; // ์บ๋Ÿฌ์…€ ์ดˆ๊ธฐํ™” initBetterCarousel(container, images.length); }; /** * ๋ชจ๋‹ฌ ๋‹ซ๊ธฐ (URL ์ •๋ฆฌ ๊ธฐ๋Šฅ ํฌํ•จ) */ window.closeModal = () => { document.getElementById('product-modal').classList.add('hidden'); document.body.style.overflow = 'auto'; const cleanUrl = window.location.origin + window.location.pathname; window.history.replaceState(null, '', cleanUrl); }; 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' }); }; // ์ดˆ๊ธฐ ์‹คํ–‰ 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(); // ์ดˆ๊ธฐ ์‹คํ–‰ ์‹œ ํ˜ธ์ถœ document.addEventListener('DOMContentLoaded', () => { renderProducts(currentPage); checkUrlAndOpenModal(); }); function checkUrlAndOpenModal() { const params = new URLSearchParams(window.location.search); const productId = params.get('id'); // URL์—์„œ ๊ฐ€์ ธ์˜จ ID (๋ฌธ์ž์—ด) if (productId) { // ๋ฐ์ดํ„ฐ์˜ id์™€ URL์˜ id๋ฅผ ๋ชจ๋‘ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋น„๊ต const product = products.find((p) => String(p.id) === productId); if (product) { // DOM ๋ Œ๋”๋ง ์‹œ๊ฐ„์„ ๊ณ ๋ คํ•ด ์•ฝ๊ฐ„์˜ ์ง€์—ฐ ํ›„ ๋ชจ๋‹ฌ ์˜คํ”ˆ setTimeout(() => openModal(product.id), 100); } } } // [์ตœ์ข… ID ์ƒ์„ฑ๊ธฐ] ๋งค๋ฒˆ ์™„์ „ํžˆ ์ƒˆ๋กœ์šด 8์ž๋ฆฌ ๋‚œ์ˆ˜ ์ถœ๋ ฅ const newId = Math.random().toString(36).substring(2, 10); console.log(`%c[NEW ID for data.js]: ${newId}`, "color: #137fec; font-weight: bold; border: 1px solid #137fec; padding: 2px 5px; border-radius: 4px;");