import products from '/data.js'; const ITEMS_PER_PAGE = 8; 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 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 baseColor = STATUS_COLOR[key] ?? ''; const chip = document.createElement('button'); chip.className = ` status-chip px-4 py-2 rounded-full text-sm font-medium transition border ${isActive ? baseColor : 'bg-slate-50 text-slate-600 border-slate-200'} `; 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(''); // HTML ์ฃผ์ž… document.getElementById('modal-main-carousel').innerHTML = mainImagesHtml; document.getElementById('modal-thumbnails').innerHTML = thumbnailsHtml; document.getElementById('modal-dots').innerHTML = dotsHtml; // ํ…์ŠคํŠธ ์ •๋ณด ์ฃผ์ž… (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();