first commit 2

This commit is contained in:
hmw1001
2026-06-11 18:47:38 +09:00
parent c768729ce6
commit 6f534e33a6
11095 changed files with 1595758 additions and 0 deletions
@@ -0,0 +1,99 @@
function initProducts(productsData) {
const preview = document.getElementById('hover-preview');
const previewGrid = document.getElementById('preview-grid');
const hotspotLayer = document.getElementById('hotspot-layer');
const coordDisplay = document.getElementById('coord-display');
const map = document.getElementById('main-map');
let isFixed = false;
const noImg = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMjIyIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiM0NDQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmb250LWZhbWl5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjI0Ij5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=";
const groups = {};
productsData.forEach(p => {
const key = `${parseFloat(p.xPct).toFixed(1)}_${parseFloat(p.yPct).toFixed(1)}`;
if (!groups[key]) groups[key] = [];
groups[key].push(p);
});
Object.keys(groups).forEach(key => {
const group = groups[key];
const hs = document.createElement('div');
hs.className = 'hotspot';
hs.style.left = group[0].xPct + '%';
hs.style.top = group[0].yPct + '%';
const updateContent = (showFullContent) => {
const hasContent = group.some(p => p.content && p.content.trim() !== "");
const isSingle = group.length === 1;
if (showFullContent && hasContent) {
previewGrid.className = `products-grid full-html-mode ${isSingle ? 'single-item' : 'multi-items'}`;
previewGrid.innerHTML = group.map(p => {
console.log( p.content)
if (p.content && p.content.trim() !== "") {
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
return `<div class="product-item item-vertical" ${linkAttr}>
<div class="editor-content-view">${p.content}</div>
<div class="info-content">
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
${p.link ? `<div class="text-[11px] text-blue-500 mt-2" onclick="location.href='${p.link}'" style="cursor:pointer;">자세히 보기 ></div>` : ''}
</div>
</div>`;
} else {
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
return `<div class="product-item item-vertical" ${linkAttr}>
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
<div class="info-content">
<!-- <div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>-->
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
</div>
</div>`;
}
}).join('<hr class="content-divider">');
} else {
const gridClass = isSingle ? 'single-item' : 'multi-items';
previewGrid.className = `products-grid ${gridClass}`;
previewGrid.innerHTML = group.map(p => {
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
return `<div class="product-item item-vertical" ${linkAttr}>
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
<div class="info-content">
<!-- <div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>-->
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
</div>
</div>`;
}).join('');
}
};
hs.onmouseenter = () => { if (!isFixed) { updateContent(false); preview.style.display = 'block'; preview.classList.remove('fixed-mode'); } };
hs.onmouseleave = () => { if (!isFixed) preview.style.display = 'none'; };
hs.onclick = (e) => {
e.stopPropagation();
isFixed = true;
updateContent(true);
preview.classList.add('fixed-mode');
preview.style.display = 'flex';
};
hotspotLayer.appendChild(hs);
});
document.addEventListener('click', (e) => {
if (!preview.contains(e.target)) {
isFixed = false; preview.style.display = 'none'; preview.classList.remove('fixed-mode');
}
});
map.onmousemove = (e) => {
const r = map.getBoundingClientRect();
// coordDisplay.textContent = `X: ${((e.clientX-r.left)/r.width*100).toFixed(1)}%, Y: ${((e.clientY-r.top)/r.height*100).toFixed(1)}%`;
};
map.onclick = (e) => {
const r = map.getBoundingClientRect();
prompt("좌표:", `xPct: ${((e.clientX-r.left)/r.width*100).toFixed(1)}, yPct: ${((e.clientY-r.top)/r.height*100).toFixed(1)}`);
};
}
@@ -0,0 +1,56 @@
function initProducts(productsData) {
const map = document.getElementById('main-map');
const hotspotLayer = document.getElementById('hotspot-layer');
const preview = document.getElementById('hover-preview');
const previewGrid = document.getElementById('preview-grid');
// const coordDisplay = document.getElementById('coord-display');
// 그룹화 로직
const groups = {};
productsData.forEach(p => {
const key = `${parseFloat(p.xPct).toFixed(1)}_${parseFloat(p.yPct).toFixed(1)}`;
if (!groups[key]) groups[key] = [];
groups[key].push(p);
});
Object.keys(groups).forEach(key => {
const group = groups[key];
const hs = document.createElement('div');
hs.className = 'hotspot';
hs.style.left = group[0].xPct + '%';
hs.style.top = group[0].yPct + '%';
hs.style.transform = 'translate(-50%, -50%)';
hs.onmouseenter = () => {
preview.style.display = 'block';
previewGrid.innerHTML = group.map(p => `
<div class="product-item">
<div class="product-img-box"><img src="${p.img_url}" onerror="this.src='https://via.placeholder.com/200x120?text=No+Image'"></div>
<div class="text-[10px] text-blue-400 font-bold mb-1">${p.page}</div>
<h4 class="text-white text-[13px] font-bold truncate">${p.title}</h4>
<p class="text-slate-500 text-[10px] line-clamp-2">${p.desc}</p>
</div>
`).join('');
};
hs.onmouseleave = () => preview.style.display = 'none';
hs.onmousemove = (e) => {
let x = e.clientX + 30;
let y = e.clientY + 30;
if (x + 540 > window.innerWidth) x = e.clientX - 560;
if (y + preview.offsetHeight > window.innerHeight) y = window.innerHeight - preview.offsetHeight - 20;
preview.style.left = x + 'px';
preview.style.top = y + 'px';
};
hotspotLayer.appendChild(hs);
});
// 좌표 도구
// map.onmousemove = (e) => {
// const r = map.getBoundingClientRect();
// coordDisplay.textContent = `X: ${((e.clientX-r.left)/r.width*100).toFixed(1)}%, Y: ${((e.clientY-r.top)/r.height*100).toFixed(1)}%`;
// };
// map.onclick = (e) => {
// const r = map.getBoundingClientRect();
// prompt("좌표:", `xPct: ${((e.clientX-r.left)/r.width*100).toFixed(1)}, yPct: ${((e.clientY-r.top)/r.height*100).toFixed(1)}`);
// };
}
@@ -0,0 +1,103 @@
function initProducts(productsData) {
const map = document.getElementById('main-map');
const hotspotLayer = document.getElementById('hotspot-layer');
const preview = document.getElementById('hover-preview');
const previewGrid = document.getElementById('preview-grid');
// const coordDisplay = document.getElementById('coord-display');
let isFixed = false; // 팝업 고정 상태 변수
const groups = {};
productsData.forEach(p => {
const key = `${parseFloat(p.x_pct || p.xPct).toFixed(1)}_${parseFloat(p.y_pct || p.yPct).toFixed(1)}`;
if (!groups[key]) groups[key] = [];
groups[key].push(p);
});
Object.keys(groups).forEach(key => {
const group = groups[key];
const hs = document.createElement('div');
hs.className = 'hotspot';
hs.style.left = (group[0].x_pct || group[0].xPct) + '%';
hs.style.top = (group[0].y_pct || group[0].yPct) + '%';
hs.style.transform = 'translate(-50%, -50%)';
// 팝업 내용 생성 함수
const updateContent = () => {
const gridClass = group.length === 1 ? 'single-item' : 'multi-items';
previewGrid.className = `products-grid ${gridClass}`;
previewGrid.innerHTML = group.map(p => `
<div class="product-item">
<div class="product-img-box"><img src="${p.img_url}" onerror="this.src='https://via.placeholder.com/200x120?text=No+Image'"></div>
<div class="info-content">
<div class="text-[11px] text-blue-400 font-bold mb-1">${p.page}</div>
<h4 class="text-white text-[16px] font-bold mb-2">${p.title}</h4>
<p class="text-slate-400 text-[13px] leading-relaxed">${p.desc}</p>
</div>
</div>
`).join('');
};
// 마우스 진입 (호버 시작)
hs.onmouseenter = (e) => {
if (isFixed) return;
updateContent();
preview.style.display = 'block';
};
// 마우스 이동 (팝업이 마우스를 따라다님)
hs.onmousemove = (e) => {
if (isFixed) return;
let x = e.clientX + 20;
let y = e.clientY - (preview.offsetHeight / 2);
if (x + 560 > window.innerWidth) x = e.clientX - 580;
if (y < 20) y = 20;
preview.style.left = x + 'px';
preview.style.top = y + 'px';
};
// 마우스 이탈 (호버 종료)
hs.onmouseleave = () => {
if (isFixed) return;
preview.style.display = 'none';
};
// 클릭 시 고정
hs.onclick = (e) => {
e.stopPropagation();
isFixed = true;
updateContent();
preview.style.display = 'block';
// 고정 시에는 화면 중앙 근처 적절한 위치에 배치
let x = e.clientX + 30;
let y = e.clientY - (preview.offsetHeight / 2);
if (x + 720 > window.innerWidth) x = e.clientX - 730;
if (y < 20) y = 20;
preview.style.left = x + 'px';
preview.style.top = y + 'px';
preview.classList.add('fixed-mode');
};
hotspotLayer.appendChild(hs);
});
// 외부 클릭 시 고정 해제 및 닫기
document.addEventListener('click', (e) => {
if (!preview.contains(e.target)) {
isFixed = false;
preview.style.display = 'none';
preview.classList.remove('fixed-mode');
}
});
// // 좌표 도구
// map.onmousemove = (e) => {
// const r = map.getBoundingClientRect();
// coordDisplay.textContent = `X: ${((e.clientX-r.left)/r.width*100).toFixed(1)}%, Y: ${((e.clientY-r.top)/r.height*100).toFixed(1)}%`;
// };
// map.onclick = (e) => {
// const r = map.getBoundingClientRect();
// prompt("좌표:", `xPct: ${((e.clientX-r.left)/r.width*100).toFixed(1)}, yPct: ${((e.clientY-r.top)/r.height*100).toFixed(1)}`);
// };
}
@@ -0,0 +1,162 @@
/**
* [최종 통합본] 제품 사이드 메뉴 및 메인 맵 연동 모듈 (products2.js)
* 1: 중앙팝업, 2: 점선연결, 3: 제자리확대, 4: 1초 지연 후 중앙팝업
*/
(function() {
// 💡 설정: 원하는 모드 번호를 입력하세요.
const POPUP_MODE = 4;
let popupTimer = null; // 4번 모드용 타이머
// 모든 효과와 타이머를 제거하는 청소기 함수
function clearAllPopups() {
// 실행 중인 타이머가 있으면 취소
if (popupTimer) {
clearTimeout(popupTimer);
popupTimer = null;
}
// 핫스팟 숨김
const hotspot = document.querySelector('.product-hotspot');
if (hotspot) hotspot.style.display = 'none';
// 생성된 클론 이미지(모드 1, 4) 삭제
const clones = document.querySelectorAll('#active-popup-img');
clones.forEach(c => c.remove());
// 모든 아이템의 클래스 및 스타일 초기화
const items = document.querySelectorAll('.product-item');
items.forEach(item => {
item.classList.remove('show-connector', 'active');
const img = item.querySelector('.product-img img');
if (img) {
img.classList.remove('mode-self-zoom');
img.style.transform = '';
}
});
}
function initProductEvents() {
const productItems = document.querySelectorAll('.product-section .product-item');
const mainArea = document.querySelector('.product-main');
const section = document.querySelector('.product-section');
if (!productItems.length || !mainArea) return;
productItems.forEach(item => {
const imgElement = item.querySelector('.product-img img');
const imgSrc = item.dataset.img || (imgElement ? imgElement.src : '');
const proLink = item.dataset.prolink;
item.addEventListener('mouseenter', function() {
clearAllPopups(); // 새로 시작하기 전 초기화
const x1 = this.dataset.x1;
const y1 = this.dataset.y1;
if (x1 && y1) {
// 1. 핫스팟(블루 스폿)은 어떤 모드든 즉시 표시
const hotspot = document.querySelector('.product-hotspot');
if (hotspot) {
hotspot.style.left = x1 + '%';
hotspot.style.top = y1 + '%';
hotspot.style.display = 'block';
}
// 2. 모드별 효과 실행
if (POPUP_MODE === 4) {
// [모드 4] 1초 지연 후 중앙 팝업
popupTimer = setTimeout(() => {
if (imgElement) {
const clone = imgElement.cloneNode(true);
clone.id = 'active-popup-img';
clone.src = imgSrc;
if (proLink) {
clone.style.cursor = 'pointer';
clone.style.pointerEvents = 'auto';
clone.onclick = () => { location.href = proLink; };
}
mainArea.appendChild(clone);
requestAnimationFrame(() => clone.classList.add('active'));
}
}, 1000); // 1000ms = 1초 대기
} else if (POPUP_MODE === 1 && imgElement) {
// [모드 1] 즉시 중앙 팝업
const clone = imgElement.cloneNode(true);
clone.id = 'active-popup-img';
clone.src = imgSrc;
if (proLink) {
clone.style.cursor = 'pointer';
clone.style.pointerEvents = 'auto';
clone.onclick = () => { location.href = proLink; };
}
mainArea.appendChild(clone);
requestAnimationFrame(() => clone.classList.add('active'));
} else if (POPUP_MODE === 2) {
// [모드 2] 점선 연결선 정밀 계산
const rect = this.getBoundingClientRect();
const mainRect = mainArea.getBoundingClientRect();
const targetX = mainRect.left + (mainRect.width * (parseFloat(x1) / 100));
const targetY = mainRect.top + (mainRect.height * (parseFloat(y1) / 100));
const isLeft = this.closest('.product-side').classList.contains('left');
const startX = isLeft ? rect.right : rect.left;
const startY = rect.top + (rect.height / 2);
const dx = targetX - startX;
const dy = targetY - startY;
const dist = Math.sqrt(dx * dx + dy * dy);
let angle = Math.atan2(dy, dx) * 180 / Math.PI;
if (!isLeft) angle += 180;
this.style.setProperty('--line-width', dist + 'px');
this.style.setProperty('--line-angle', angle + 'deg');
this.classList.add('show-connector');
requestAnimationFrame(() => this.classList.add('active'));
if (imgElement) imgElement.style.transform = 'scale(1.2)';
if (imgElement && proLink) {
imgElement.style.cursor = 'pointer';
imgElement.onclick = () => { location.href = proLink; };
}
} else if (POPUP_MODE === 3 && imgElement) {
// [모드 3] 제자리 600 확대
imgElement.src = imgSrc;
imgElement.classList.add('mode-self-zoom');
if (proLink) {
imgElement.style.cursor = 'pointer';
imgElement.style.pointerEvents = 'auto';
imgElement.onclick = () => { location.href = proLink; };
}
}
}
});
// 아이템 자체 클릭 시에도 이동 (이미지 외 영역 클릭 대비)
if (proLink) {
item.style.cursor = 'pointer';
item.addEventListener('click', function(e) {
if (e.target.tagName !== 'IMG') {
location.href = proLink;
}
});
}
// 마우스가 아이템을 벗어나면 즉시 청소 (타이머 취소 포함)
item.addEventListener('mouseleave', clearAllPopups);
});
// 섹션 전체를 벗어나도 청소 (안전장치)
if (section) {
section.addEventListener('mouseleave', clearAllPopups);
}
}
// 문서 로드 완료 시 실행
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initProductEvents);
} else {
initProductEvents();
}
})();
@@ -0,0 +1,64 @@
function initProductEvents() {
// 💡 설정: 1(메인중앙), 2(점선), 3(제자리확대)
const POPUP_MODE = 1;
const productItems = document.querySelectorAll('.product-section .product-item');
const mainArea = document.querySelector('.product-main');
if (!productItems.length) return;
productItems.forEach(item => {
// 💡 [수정] 데이터 속성에서 이미지 경로 직접 읽기 (없으면 내부 img 사용)
const imgSrc = item.dataset.img || item.querySelector('.product-img img').src;
const imgElement = item.querySelector('.product-img img');
item.addEventListener('mouseenter', function() {
clearAllPopups();
const x1 = this.dataset.x1;
const y1 = this.dataset.y1;
if (x1 && y1) {
ProductModule.setHotspot(x1, y1);
if (POPUP_MODE === 1 && mainArea) {
// [모드 1] 메인 중앙 복제 팝업
const clone = document.createElement('img');
clone.src = imgSrc; // 💡 전달받은 경로로 로드
clone.id = 'active-popup-img';
mainArea.appendChild(clone);
requestAnimationFrame(() => clone.classList.add('active'));
} else if (POPUP_MODE === 2) {
// [모드 2] 점선 연결 (기존 로직 동일)
const rect = this.getBoundingClientRect();
const mainRect = mainArea.getBoundingClientRect();
const targetX = mainRect.left + (mainRect.width * (parseFloat(x1) / 100));
const targetY = mainRect.top + (mainRect.height * (parseFloat(y1) / 100));
const isLeft = this.closest('.product-side').classList.contains('left');
const startX = isLeft ? rect.right : rect.left;
const dx = targetX - startX;
const dy = targetY - (rect.top + rect.height/2);
let angle = Math.atan2(dy, dx) * 180 / Math.PI;
if (!isLeft) angle += 180;
this.style.setProperty('--line-width', Math.sqrt(dx*dx + dy*dy) + 'px');
this.style.setProperty('--line-angle', angle + 'deg');
this.classList.add('show-connector');
requestAnimationFrame(() => this.classList.add('active'));
} else if (POPUP_MODE === 3) {
// 💡 [모드 3] 제자리 400x400 확대
if (imgElement) {
imgElement.src = imgSrc; // 💡 이미지 경로 갱신
imgElement.classList.add('mode-self-zoom');
}
}
}
});
item.addEventListener('mouseleave', function() {
clearAllPopups();
if (imgElement) imgElement.classList.remove('mode-self-zoom');
});
});
}
@@ -0,0 +1,99 @@
function initProducts(productsData) {
const map = document.getElementById('main-map');
const hotspotLayer = document.getElementById('hotspot-layer');
const preview = document.getElementById('hover-preview');
const previewGrid = document.getElementById('preview-grid');
let isFixed = false;
const noImg = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjMjIyIi8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZpbGw9IiM0NDQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGR5PSIuM2VtIiBmb250LWZhbWl5PSJzYW5zLXNlcmlmIiBmb250LXNpemU9IjI0Ij5ObyBJbWFnZTwvdGV4dD48L3N2Zz4=";
// 1. 그룹화 로직
const groups = {};
productsData.forEach(p => {
const key = `${parseFloat(p.xPct).toFixed(1)}_${parseFloat(p.yPct).toFixed(1)}`;
if (!groups[key]) groups[key] = [];
groups[key].push(p);
});
// 2. 핫스팟 생성 및 이벤트
Object.keys(groups).forEach(key => {
const group = groups[key];
const hs = document.createElement('div');
hs.className = 'hotspot';
hs.style.left = group[0].xPct + '%';
hs.style.top = group[0].yPct + '%';
hs.style.transform = 'translate(-50%, -50%)';
const updateContent = (showFullContent) => {
const hasContent = group.some(p => p.content && p.content.trim() !== "");
const isSingle = group.length === 1;
if (showFullContent && hasContent) {
// 클릭 시: 상세 HTML 모드
previewGrid.className = `products-grid full-html-mode ${isSingle ? 'single-item' : 'multi-items'}`;
previewGrid.innerHTML = group.map(p => {
if (p.content && p.content.trim() !== "") {
return `<div class="editor-content-view">${p.content}</div>`;
} else {
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
return `<div class="product-item" ${linkAttr}>
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
<div class="info-content">
<div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
${p.link ? '<div class="text-[11px] text-blue-500 mt-2">자세히 보기 ></div>' : ''}
</div>
</div>`;
}
}).join('<hr class="content-divider">');
} else {
// 호버 시: 요약 모드
const gridClass = isSingle ? 'single-item' : 'multi-items';
previewGrid.className = `products-grid ${gridClass}`;
previewGrid.innerHTML = group.map(p => {
const linkAttr = (p.link) ? `onclick="location.href='${p.link}'" style="cursor:pointer;"` : "";
return `<div class="product-item" ${linkAttr}>
<div class="product-img-box"><img src="${p.img_url}" onerror="this.onerror=null; this.src='${noImg}';"></div>
<div class="info-content">
<div class="text-[12px] text-blue-400 font-bold mb-2">${p.page}</div>
<h4 class="text-white text-[18px] font-bold mb-3">${p.title}</h4>
<p class="text-slate-400 text-[14px] leading-relaxed">${p.desc}</p>
</div>
</div>`;
}).join('');
}
};
hs.onmouseenter = () => {
if (isFixed) return;
updateContent(false);
preview.style.display = 'block';
preview.classList.remove('fixed-mode');
};
hs.onmouseleave = () => {
if (isFixed) return;
preview.style.display = 'none';
};
hs.onclick = (e) => {
e.stopPropagation();
isFixed = true;
updateContent(true);
preview.classList.add('fixed-mode');
preview.style.display = 'flex';
};
hotspotLayer.appendChild(hs);
});
// 외부 클릭 시 닫기
document.addEventListener('click', (e) => {
if (!preview.contains(e.target)) {
isFixed = false;
preview.style.display = 'none';
preview.classList.remove('fixed-mode');
}
});
}