즐겨찾기
비교하고 싶은 상품에 메모를 남겨 두세요. alphabotcoreai.pro
✓
장바구니에 담았어요
우측 상단에서 장바구니를 확인하세요.
쿠키 동의
더 나은 서비스 제공을 위해 쿠키를 사용합니다. 설정에서 세부 항목을 선택할 수 있습니다.
';
}
try {
const foot = await fetch('./footer.html').then(r=>r.text());
document.querySelector('footer[data-include]').innerHTML = foot;
} catch(e) {
document.querySelector('footer[data-include]').innerHTML = '';
}
document.body.addEventListener('click', (e)=>{
const tbtn = e.target.closest('[data-theme-toggle]');
if (tbtn) {
const cur = localStorage.getItem('theme')||'light';
const next = cur==='light'?'dark':'light';
localStorage.setItem('theme', next);
document.documentElement.classList.toggle('dark', next==='dark');
}
});
}
includePartials();
const notesKey = 'favoriteNotes';
function getNotes() { return JSON.parse(localStorage.getItem(notesKey)||'{}'); }
function setNotes(o) { localStorage.setItem(notesKey, JSON.stringify(o)); }
const favKey = 'favorites';
function getFavs(){ return JSON.parse(localStorage.getItem(favKey)||'[]'); }
function setFavs(arr){ localStorage.setItem(favKey, JSON.stringify(arr)); }
const autosaveTimers = new Map();
async function loadFavs() {
const list = document.getElementById('favList');
const ids = getFavs();
const statusBar = document.getElementById('statusBar');
statusBar.textContent = `선택한 상품 ${ids.length}개`;
let data = [];
try {
const all = await fetch('./catalog.json').then(r=>r.json());
data = all.filter(d=>ids.includes(String(d.id)));
} catch(e) {
list.innerHTML = `데이터를 불러오지 못했습니다. 잠시 후 다시 시도해주세요.`;
return;
}
render(data);
}
function imageFor(it){
const src = (it.images && it.images[0]) || './images/maximum_ditailes_of_this_image.professional_photography_studio_softbox_tripod_background_lightroom_ready_ultra_hd_high_contrast_minimal_black_white.jpg';
return src;
}
function render(items) {
const list = document.getElementById('favList');
const notes = getNotes();
if (!items.length) {
list.innerHTML = `
즐겨찾기한 상품이 없습니다.
카탈로그에서 마음에 드는 상품을 추가해 보세요.
`;
return;
}
list.innerHTML = items.map(it=>{
const nid = String(it.id);
const noteVal = (notes && notes[nid]) || '';
const priceText = typeof it.price === 'number' ? it.price.toLocaleString() : (it.price||'');
return `
${it.title||'상품명 미지정'}
${priceText?`${priceText}원 • `:''}${it.category||'기타'}
`;
}).join('');
}
function showToast(msg){
const el = document.getElementById('toast');
const msgEl = document.getElementById('toastMsg');
msgEl.textContent = msg || '완료되었습니다';
el.classList.remove('hidden');
clearTimeout(showToast._t);
showToast._t = setTimeout(()=>{ el.classList.add('hidden'); }, 2600);
}
document.getElementById('toastClose').addEventListener('click', ()=> {
document.getElementById('toast').classList.add('hidden');
});
function openModal({title, body, actions=[]}){
const overlay = document.getElementById('modalOverlay');
const t = document.getElementById('modalTitle');
const b = document.getElementById('modalBody');
const a = document.getElementById('modalActions');
t.textContent = title||'알림';
if (typeof body === 'string') b.innerHTML = body; else { b.innerHTML=''; b.appendChild(body); }
a.innerHTML = '';
actions.forEach(btn=>{
const el = document.createElement('button');
el.textContent = btn.label;
el.className = btn.className || 'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700';
el.addEventListener('click', ()=>{ if (btn.onClick) btn.onClick(); });
a.appendChild(el);
});
overlay.classList.remove('hidden');
const close = ()=> overlay.classList.add('hidden');
document.getElementById('modalClose').onclick = close;
overlay.onclick = (e)=>{ if (e.target===overlay) close(); };
return { close };
}
function confirmDelete(id){
const { close } = openModal({
title: '삭제 확인',
body: `해당 상품을 즐겨찾기에서 제거하시겠어요? 메모는 유지됩니다.
`,
actions: [
{ label:'취소', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:()=>close() },
{ label:'삭제', className:'px-4 py-2 rounded-lg bg-red-600 text-white', onClick:()=>{ removeFav(id); close(); } }
]
});
}
function removeFav(id){
const ids = new Set(getFavs()); ids.delete(String(id));
setFavs([...ids]);
loadFavs();
showToast('즐겨찾기에서 삭제했어요');
}
document.addEventListener('click', (e)=>{
const rid = e.target.getAttribute('data-remove');
if (rid) { confirmDelete(rid); }
const sid = e.target.getAttribute('data-save');
if (sid) {
const ta = document.querySelector(`textarea[data-note="${sid}"]`);
const notes = getNotes(); notes[sid] = (ta.value||'').slice(0,600); setNotes(notes);
e.target.textContent = '저장됨';
setTimeout(()=>{e.target.textContent='메모 저장'}, 1000);
const ind = document.querySelector(`[data-indicator="${sid}"]`);
if (ind){ ind.textContent='수정사항이 저장되었습니다.'; ind.style.opacity='1'; setTimeout(()=>ind.style.opacity='0',1200); }
}
const cid = e.target.getAttribute('data-cart');
if (cid) {
const cart = JSON.parse(localStorage.getItem('cart')||'[]');
const f = cart.find(x=>String(x.id)===String(cid)); if (f) f.qty=(f.qty||1)+1; else cart.push({id:String(cid), qty:1});
localStorage.setItem('cart', JSON.stringify(cart));
showToast('장바구니에 담았어요');
}
});
document.addEventListener('input', (e)=>{
const ta = e.target.closest('textarea[data-note]');
if (!ta) return;
const id = ta.getAttribute('data-note');
const ind = document.querySelector(`[data-indicator="${id}"]`);
if (ind){ ind.textContent='자동 저장 대기중...'; ind.style.opacity='1'; }
if (autosaveTimers.has(id)) clearTimeout(autosaveTimers.get(id));
let countdown = 2;
if (ind){ ind.textContent = `자동 저장: ${countdown}s`; }
const tick = setInterval(()=>{ countdown--; if (ind){ ind.textContent = countdown>0 ? `자동 저장: ${countdown}s` : '저장 중...'; } if (countdown<=0){ clearInterval(tick); } }, 1000);
const t = setTimeout(()=>{
const notes = getNotes(); notes[id] = (ta.value||'').slice(0,600); setNotes(notes);
if (ind){ ind.textContent='자동 저장됨'; setTimeout(()=>{ ind.style.opacity='0'; }, 1200); }
}, 2000);
autosaveTimers.set(id, t);
});
window.addEventListener('storage', (e)=>{
if (e.key===favKey){ loadFavs(); }
if (e.key===notesKey){ loadFavs(); }
});
document.getElementById('exportNotes').addEventListener('click', ()=>{
const notes = getNotes();
const blob = new Blob([JSON.stringify(notes,null,2)], {type:'application/json'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'favorite_notes.json';
document.body.appendChild(a); a.click(); a.remove();
URL.revokeObjectURL(url);
});
document.getElementById('clearAllFavs').addEventListener('click', ()=>{
const favs = getFavs();
if (!favs.length){ showToast('삭제할 항목이 없습니다'); return; }
const { close } = openModal({
title: '전체 삭제',
body: `모든 즐겨찾기를 삭제하시겠어요? 메모는 유지됩니다.
`,
actions: [
{ label:'취소', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:()=>close() },
{ label:'전체 삭제', className:'px-4 py-2 rounded-lg bg-red-600 text-white', onClick:()=>{ setFavs([]); loadFavs(); close(); showToast('전체 삭제 완료'); } }
]
});
});
document.getElementById('shareFavs').addEventListener('click', ()=>{
const wrap = document.createElement('div');
wrap.innerHTML = `
`;
const { close } = openModal({
title: '즐겨찾기 공유',
body: wrap,
actions: [
{ label:'닫기', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:()=>close() },
{ label:'공유 링크 생성', className:'px-4 py-2 rounded-lg bg-black text-white dark:bg-white dark:text-black', onClick:()=>{
const form = wrap.querySelector('#shareForm');
const name = form.name.value.trim();
const email = form.email.value.trim();
const msg = form.msg.value.trim();
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (name.length<2){ alert('이름을 2자 이상 입력하세요.'); return; }
if (!emailRe.test(email)){ alert('올바른 이메일을 입력하세요.'); return; }
const payload = { sender:{name,email}, msg, favorites:getFavs(), notes:getNotes(), ts:Date.now() };
const encoded = encodeURIComponent(btoa(unescape(encodeURIComponent(JSON.stringify(payload)))));
const shareUrl = location.origin + location.pathname + '#share=' + encoded;
const body = document.createElement('div');
body.innerHTML = `
공유용 데이터가 생성되었습니다.
아래 링크를 복사해 팀원과 공유하세요.
${shareUrl}
`;
const modal = openModal({ title:'공유 링크', body, actions:[{label:'닫기', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:()=>modal.close()}]});
body.querySelector('#copyShare').onclick = ()=>{ navigator.clipboard.writeText(shareUrl).then(()=>showToast('복사되었습니다')); };
}
}
]
});
});
document.addEventListener('click', (e)=>{
if (e.target && e.target.id==='openGuide'){
openModal({
title:'즐겨찾기 안내',
body:`
- 카탈로그에서 하트 아이콘을 눌러 즐겨찾기에 추가하세요.
- 여기에서 메모를 작성하고 비교할 수 있어요.
- 장바구니에 담아 예약 절차를 이어가세요.
`,
actions:[{label:'닫기', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:function(){ document.getElementById('modalOverlay').classList.add('hidden'); }}]
});
}
});
function initCookieBar(){
const state = JSON.parse(localStorage.getItem('cookieConsent')||'null');
if (!state){
document.getElementById('cookieBar').classList.remove('hidden');
}
document.getElementById('cookieAccept').onclick = ()=>{
localStorage.setItem('cookieConsent', JSON.stringify({necessary:true, analytics:true, marketing:true, ts:Date.now()}));
document.getElementById('cookieBar').classList.add('hidden');
showToast('쿠키가 허용되었습니다');
};
document.getElementById('cookieSettings').onclick = ()=>{
const wrap = document.createElement('div');
wrap.innerHTML = `
`;
const { close } = openModal({
title:'쿠키 설정',
body: wrap,
actions:[
{ label:'취소', className:'px-4 py-2 rounded-lg border border-gray-300 dark:border-neutral-700', onClick:()=>close() },
{ label:'저장', className:'px-4 py-2 rounded-lg bg-black text-white dark:bg-white dark:text-black', onClick:()=>{
const analytics = wrap.querySelector('#ckAnalytics').checked;
const marketing = wrap.querySelector('#ckMarketing').checked;
localStorage.setItem('cookieConsent', JSON.stringify({necessary:true, analytics, marketing, ts:Date.now()}));
document.getElementById('cookieBar').classList.add('hidden');
close();
showToast('쿠키 설정이 저장되었습니다');
}
}
]
});
};
}
function parseShareHash(){
const hash = location.hash || '';
const m = hash.match(/#share=([^&]+)/);
if (!m) return;
try{
const decoded = JSON.parse(decodeURIComponent(escape(atob(decodeURIComponent(m[1])))));
const { favorites, notes } = decoded || {};
if (Array.isArray(favorites)) setFavs(favorites.map(String));
if (notes && typeof notes==='object') setNotes(notes);
loadFavs();
showToast('공유 데이터를 불러왔습니다');
}catch(e){}
}
loadFavs();
initCookieBar();
parseShareHash();