Refactor: JS 버그 수정 23건 + 이미지 최적화 + 크리에이터 사인 추가
- JS 논리 오류 수정: gallery.js lightbox 초기화 타이밍, 터치 리스너 누적, IntersectionObserver 통합 - XSS 방지: qna.js showNoResults innerHTML → textContent, 정규식 이스케이프 추가 - 안전성 개선: popup.js ESC 가드, portfolio.js getIframe optional chaining, backgrounds/props null 가드 - 이미지 최적화: 스튜디오 12장 WebP 압축 (4.0MB → 2.2MB, 46% 감소) - 360 이미지: git 히스토리에서 원본 복구 후 4096×2048 리사이즈 (해상도 4.6배 향상) - 360 뷰어: image-rendering auto 전환, naturalWidth/Height 기반 렌더링으로 품질 개선 - 크리에이터 사인 추가: 얌하 (3.3KB), 구슬요 (5.9KB) WebP 변환 및 마키 삽입 - 불필요 코드 제거: gallery.js 미사용 함수 6개 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ -298,7 +298,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@ -226,7 +226,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -326,10 +326,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .info-grid {
|
||||
background: var(--dark-surface);
|
||||
border: 1px solid var(--glass-border);
|
||||
|
||||
@ -412,13 +412,6 @@ body {
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
text-align: center;
|
||||
padding-top: var(--spacing-xl);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* 백업/기본 푸터 */
|
||||
.site-footer {
|
||||
background: var(--text-primary);
|
||||
@ -1501,7 +1494,7 @@ body {
|
||||
|
||||
.streamer-card {
|
||||
background: var(--bg-white);
|
||||
border-radius: var(--radius-xl);
|
||||
border-radius: var(--border-radius-xl);
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: var(--spacing-2xl);
|
||||
text-align: center;
|
||||
@ -1577,7 +1570,7 @@ body {
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-lg);
|
||||
border-radius: var(--border-radius-lg);
|
||||
font-size: var(--font-sm);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
@ -1686,7 +1679,7 @@ body {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: var(--radius-lg);
|
||||
border-radius: var(--border-radius-lg);
|
||||
overflow: hidden;
|
||||
background: var(--bg-light);
|
||||
box-shadow: var(--shadow-sm);
|
||||
|
||||
@ -507,10 +507,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .contact-card {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
backdrop-filter: blur(20px);
|
||||
|
||||
@ -929,10 +929,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .gallery-grid {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@ -817,10 +817,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tab-btn {
|
||||
color: var(--dark-text-secondary);
|
||||
border-color: var(--glass-border);
|
||||
|
||||
@ -419,10 +419,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .faq-search {
|
||||
background: var(--dark-surface);
|
||||
border: 1px solid var(--glass-border);
|
||||
|
||||
@ -2127,10 +2127,6 @@
|
||||
/* ========================================
|
||||
다크모드
|
||||
======================================== */
|
||||
[data-theme="dark"] .page-header {
|
||||
background: var(--dark-header-gradient);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .service-package {
|
||||
background-color: var(--dark-surface);
|
||||
border: 1px solid var(--glass-border);
|
||||
|
||||
@ -215,7 +215,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
BIN
images/sign/구슬요.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
images/sign/얌하.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 248 KiB After Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 384 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 382 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 459 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 302 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 889 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 862 KiB |
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 503 KiB After Width: | Height: | Size: 238 KiB |
82
index.html
@ -686,39 +686,45 @@
|
||||
|
||||
<div class="signs-marquee">
|
||||
<div class="signs-marquee-track">
|
||||
<!-- 원본 10개 -->
|
||||
<div class="sign-item"><img src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
<!-- 원본 12개 -->
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/얌하.webp" alt="얌하 사인"><span class="sign-name">얌하</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/구슬요.webp" alt="구슬요 사인"><span class="sign-name">구슬요</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
<!-- 복제 1 -->
|
||||
<div class="sign-item"><img src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/얌하.webp" alt="얌하 사인"><span class="sign-name">얌하</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/구슬요.webp" alt="구슬요 사인"><span class="sign-name">구슬요</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
<!-- 복제 2 -->
|
||||
<div class="sign-item"><img src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/김마늘.jpg" alt="김마늘 사인"><span class="sign-name">김마늘</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/만타.jpg" alt="만타 사인"><span class="sign-name">만타</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/문모모.jpg" alt="문모모 사인"><span class="sign-name">문모모</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/베니.jpg" alt="베니 사인"><span class="sign-name">베니</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/시에.jpg" alt="시에 사인"><span class="sign-name">시에</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/얌하.webp" alt="얌하 사인"><span class="sign-name">얌하</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/요나카.jpg" alt="요나카 사인"><span class="sign-name">요나카</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/이무지.jpg" alt="이무지 사인"><span class="sign-name">이무지</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/구슬요.webp" alt="구슬요 사인"><span class="sign-name">구슬요</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/지한이또.jpg" alt="지한이또 사인"><span class="sign-name">지한이또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/최또.jpg" alt="최또 사인"><span class="sign-name">최또</span></div>
|
||||
<div class="sign-item"><img loading="lazy" src="/images/sign/치요.jpg" alt="치요 사인"><span class="sign-name">치요</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -735,13 +741,13 @@
|
||||
<!-- 클라이언트 로고 마키 -->
|
||||
<div class="partners-marquee">
|
||||
<div class="partners-marquee-track">
|
||||
<div class="partner-logo-item"><img src="/images/partners/메가메타.jpg" alt="메가메타 - 밍글 스튜디오 클라이언트" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단 - 밍글 스튜디오 클라이언트" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/메가메타.jpg" alt="메가메타 - 밍글 스튜디오 클라이언트" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단 - 밍글 스튜디오 클라이언트" class="partner-logo"></div>
|
||||
<!-- 무한 루프용 복제 -->
|
||||
<div class="partner-logo-item"><img src="/images/partners/메가메타.jpg" alt="메가메타" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img src="/images/partners/메가메타.jpg" alt="메가메타" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/메가메타.jpg" alt="메가메타" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/메가메타.jpg" alt="메가메타" class="partner-logo"></div>
|
||||
<div class="partner-logo-item"><img loading="lazy" src="/images/partners/(주)세광종합기술단.png" alt="(주)세광종합기술단" class="partner-logo"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -839,7 +845,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -107,6 +107,8 @@
|
||||
* 이벤트 리스너 설정
|
||||
*/
|
||||
function setupEventListeners() {
|
||||
if (!elements.searchInput || !elements.imageModal || !elements.filterTags) return;
|
||||
|
||||
// 검색
|
||||
elements.searchInput.addEventListener('input', debounce((e) => {
|
||||
searchQuery = e.target.value.toLowerCase();
|
||||
|
||||
72
js/common.js
@ -35,12 +35,14 @@ async function loadComponents() {
|
||||
if (headerPlaceholder) {
|
||||
try {
|
||||
const response = await fetch('components/header.html');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const html = await response.text();
|
||||
headerPlaceholder.innerHTML = html;
|
||||
initializeNavigation(); // 헤더 로드 후 네비게이션 초기화
|
||||
initThemeToggle(); // 테마 토글 초기화
|
||||
} catch (error) {
|
||||
console.error('Error loading header:', error);
|
||||
headerPlaceholder.innerHTML = '<nav class="navbar"><div class="nav-container"><a href="/" class="nav-logo"><span>밍글 스튜디오</span></a></div></nav>';
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,11 +51,18 @@ async function loadComponents() {
|
||||
if (footerPlaceholder) {
|
||||
try {
|
||||
const response = await fetch('components/footer.html');
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
const html = await response.text();
|
||||
footerPlaceholder.innerHTML = html;
|
||||
|
||||
|
||||
// 동적 푸터의 저작권 연도 자동 업데이트
|
||||
const footerYear = footerPlaceholder.querySelector('.footer-bottom p');
|
||||
if (footerYear) {
|
||||
footerYear.innerHTML = `© ${new Date().getFullYear()} 밍글 스튜디오. All rights reserved.`;
|
||||
}
|
||||
|
||||
// 동적 푸터 로드 성공 시 백업 푸터 숨기기
|
||||
const backupFooter = document.querySelector('footer.site-footer');
|
||||
const backupFooter = document.querySelector('footer.site-footer#backupFooter');
|
||||
if (backupFooter) {
|
||||
backupFooter.style.display = 'none';
|
||||
}
|
||||
@ -67,11 +76,15 @@ async function loadComponents() {
|
||||
// ========================================
|
||||
// 네비게이션 기능
|
||||
// ========================================
|
||||
let _navInitialized = false;
|
||||
let _scrollListenerAdded = false;
|
||||
|
||||
function initializeNavigation() {
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
const navMenu = document.querySelector('.nav-menu');
|
||||
|
||||
if (hamburger && navMenu) {
|
||||
|
||||
if (hamburger && navMenu && !hamburger._listenerAdded) {
|
||||
hamburger._listenerAdded = true;
|
||||
hamburger.addEventListener('click', function() {
|
||||
const isActive = hamburger.classList.toggle('active');
|
||||
navMenu.classList.toggle('active');
|
||||
@ -90,20 +103,23 @@ function initializeNavigation() {
|
||||
});
|
||||
}
|
||||
|
||||
// 스크롤 시 네비게이션 바 스타일 변경 (RAF 최적화)
|
||||
let scrollTicking = false;
|
||||
const navbar = document.querySelector('.navbar');
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!scrollTicking) {
|
||||
requestAnimationFrame(() => {
|
||||
if (navbar) {
|
||||
navbar.classList.toggle('scrolled', window.pageYOffset > 20);
|
||||
}
|
||||
scrollTicking = false;
|
||||
});
|
||||
scrollTicking = true;
|
||||
}
|
||||
});
|
||||
// 스크롤 시 네비게이션 바 스타일 변경 (RAF 최적화) - 한 번만 등록
|
||||
if (!_scrollListenerAdded) {
|
||||
_scrollListenerAdded = true;
|
||||
let scrollTicking = false;
|
||||
window.addEventListener('scroll', () => {
|
||||
if (!scrollTicking) {
|
||||
requestAnimationFrame(() => {
|
||||
const navbar = document.querySelector('.navbar');
|
||||
if (navbar) {
|
||||
navbar.classList.toggle('scrolled', window.pageYOffset > 20);
|
||||
}
|
||||
scrollTicking = false;
|
||||
});
|
||||
scrollTicking = true;
|
||||
}
|
||||
}, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
@ -292,9 +308,6 @@ function initLazyLoading() {
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Export 함수들 (다른 스크립트에서 사용 가능)
|
||||
// ========================================
|
||||
// ========================================
|
||||
// 로딩 상태 관리
|
||||
// ========================================
|
||||
@ -327,12 +340,17 @@ function hidePageLoading() {
|
||||
}
|
||||
|
||||
function showComponentLoading(element, text = '로딩 중...') {
|
||||
element.innerHTML = `
|
||||
<div class="component-loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">${text}</div>
|
||||
</div>
|
||||
`;
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.className = 'component-loading';
|
||||
const spinner = document.createElement('div');
|
||||
spinner.className = 'loading-spinner';
|
||||
const label = document.createElement('div');
|
||||
label.className = 'loading-text';
|
||||
label.textContent = text;
|
||||
wrapper.appendChild(spinner);
|
||||
wrapper.appendChild(label);
|
||||
element.innerHTML = '';
|
||||
element.appendChild(wrapper);
|
||||
}
|
||||
|
||||
function hideComponentLoading(element, content) {
|
||||
|
||||
@ -79,16 +79,18 @@ async function handleFormSubmit(e) {
|
||||
|
||||
// 서버 전송 (mailto 기반 폴백)
|
||||
async function submitContactForm(data) {
|
||||
// mailto 링크로 이메일 클라이언트 열기
|
||||
const subject = encodeURIComponent(`[밍글 스튜디오 문의] ${data.name || '웹사이트 문의'}`);
|
||||
// 입력값에서 줄바꿈/캐리지리턴 제거 (이메일 헤더 인젝션 방지)
|
||||
const sanitize = (str) => (str || '').replace(/[\r\n]/g, ' ').trim();
|
||||
|
||||
const subject = encodeURIComponent(`[밍글 스튜디오 문의] ${sanitize(data.name) || '웹사이트 문의'}`);
|
||||
const body = encodeURIComponent(
|
||||
`이름: ${data.name || ''}\n` +
|
||||
`이메일: ${data.email || ''}\n` +
|
||||
`전화번호: ${data.phone || ''}\n` +
|
||||
`문의 유형: ${data.service || ''}\n` +
|
||||
`\n문의 내용:\n${data.message || ''}`
|
||||
`이름: ${sanitize(data.name)}\n` +
|
||||
`이메일: ${sanitize(data.email)}\n` +
|
||||
`전화번호: ${sanitize(data.phone)}\n` +
|
||||
`문의 유형: ${sanitize(data.service)}\n` +
|
||||
`\n문의 내용:\n${(data.message || '').trim()}`
|
||||
);
|
||||
window.location.href = `mailto:mingle_studio@naver.com?subject=${subject}&body=${body}`;
|
||||
window.location.href = `mailto:help@minglestudio.co.kr?subject=${subject}&body=${body}`;
|
||||
return { success: true, message: '이메일 클라이언트가 열렸습니다.' };
|
||||
}
|
||||
|
||||
@ -206,7 +208,12 @@ function isValidPhone(phone) {
|
||||
// 전화번호 자동 포맷팅
|
||||
function formatPhoneNumber(e) {
|
||||
let value = e.target.value.replace(/[^0-9]/g, '');
|
||||
|
||||
|
||||
// 최대 11자리 제한 (한국 휴대폰 번호)
|
||||
if (value.length > 11) {
|
||||
value = value.slice(0, 11);
|
||||
}
|
||||
|
||||
if (value.length >= 11) {
|
||||
// 01X-XXXX-XXXX 형태
|
||||
value = value.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
|
||||
@ -217,7 +224,7 @@ function formatPhoneNumber(e) {
|
||||
// 01X-XXX 형태
|
||||
value = value.replace(/(\d{3})(\d{0,4})/, '$1-$2');
|
||||
}
|
||||
|
||||
|
||||
e.target.value = value;
|
||||
}
|
||||
|
||||
|
||||
230
js/gallery.js
@ -12,44 +12,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// 갤러리 초기화
|
||||
function initGallery() {
|
||||
const galleryItems = document.querySelectorAll('.gallery-item');
|
||||
|
||||
|
||||
// 레이지 로딩용 옵저버를 하나만 생성 (이미지마다 생성하지 않음)
|
||||
let imageObserver = null;
|
||||
if ('IntersectionObserver' in window) {
|
||||
imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src || img.src;
|
||||
img.classList.remove('lazy');
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
galleryItems.forEach((item, index) => {
|
||||
const img = item.querySelector('.gallery-img');
|
||||
|
||||
if (!img) return;
|
||||
|
||||
// 이미지 클릭 시 라이트박스 열기
|
||||
img.addEventListener('click', () => openLightbox(index));
|
||||
|
||||
|
||||
// 이미지 로딩 에러 처리
|
||||
img.addEventListener('error', function() {
|
||||
this.src = 'images/placeholder.jpg';
|
||||
this.alt = '이미지를 불러올 수 없습니다';
|
||||
});
|
||||
|
||||
|
||||
// 레이지 로딩 구현
|
||||
if ('IntersectionObserver' in window) {
|
||||
const imageObserver = new IntersectionObserver((entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
const img = entry.target;
|
||||
img.src = img.dataset.src || img.src;
|
||||
img.classList.remove('lazy');
|
||||
imageObserver.unobserve(img);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (img.dataset.src) {
|
||||
imageObserver.observe(img);
|
||||
}
|
||||
if (imageObserver && img.dataset.src) {
|
||||
imageObserver.observe(img);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 라이트박스 기능
|
||||
let currentImageIndex = 0;
|
||||
const galleryImages = document.querySelectorAll('.gallery-img');
|
||||
let galleryImages = [];
|
||||
|
||||
function initLightbox() {
|
||||
// DOM 준비 후 갤러리 이미지 수집
|
||||
galleryImages = document.querySelectorAll('.gallery-img');
|
||||
|
||||
// 라이트박스 HTML 생성
|
||||
const lightboxHTML = `
|
||||
<div id="lightbox" class="lightbox" role="dialog" aria-label="이미지 뷰어">
|
||||
@ -62,11 +68,13 @@ function initLightbox() {
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', lightboxHTML);
|
||||
|
||||
// ESC 키로 라이트박스 닫기
|
||||
|
||||
// ESC 키로 라이트박스 닫기 (라이트박스가 열려 있을 때만)
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
if (!lightbox || !lightbox.classList.contains('active')) return;
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
if (e.key === 'ArrowLeft') previousImage();
|
||||
if (e.key === 'ArrowRight') nextImage();
|
||||
@ -158,143 +166,45 @@ function initGalleryAnimations() {
|
||||
});
|
||||
}
|
||||
|
||||
// 갤러리 필터 기능 (향후 확장용)
|
||||
function initGalleryFilters() {
|
||||
const filterButtons = document.querySelectorAll('.filter-btn');
|
||||
const galleryItems = document.querySelectorAll('.gallery-item');
|
||||
|
||||
filterButtons.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
// 활성 버튼 업데이트
|
||||
filterButtons.forEach(b => b.classList.remove('active'));
|
||||
this.classList.add('active');
|
||||
|
||||
const filter = this.dataset.filter;
|
||||
|
||||
galleryItems.forEach(item => {
|
||||
if (filter === 'all' || item.dataset.category === filter) {
|
||||
item.style.display = 'block';
|
||||
setTimeout(() => {
|
||||
item.style.opacity = '1';
|
||||
item.style.transform = 'scale(1)';
|
||||
}, 10);
|
||||
} else {
|
||||
item.style.opacity = '0';
|
||||
item.style.transform = 'scale(0.8)';
|
||||
setTimeout(() => {
|
||||
item.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 이미지 프리로딩
|
||||
function preloadImages() {
|
||||
galleryImages.forEach(img => {
|
||||
const imagePreload = new Image();
|
||||
imagePreload.src = img.src;
|
||||
});
|
||||
}
|
||||
|
||||
// 갤러리 검색 기능 (향후 확장용)
|
||||
function initGallerySearch() {
|
||||
const searchInput = document.getElementById('gallery-search');
|
||||
const galleryItems = document.querySelectorAll('.gallery-item');
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function() {
|
||||
const searchTerm = this.value.toLowerCase();
|
||||
|
||||
galleryItems.forEach(item => {
|
||||
const caption = item.querySelector('.gallery-caption');
|
||||
const alt = item.querySelector('.gallery-img').alt;
|
||||
const text = (caption ? caption.textContent : '') + ' ' + alt;
|
||||
|
||||
if (text.toLowerCase().includes(searchTerm)) {
|
||||
item.style.display = 'block';
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 이미지 로딩 상태 표시
|
||||
function showGalleryLoading() {
|
||||
const loading = document.querySelector('.gallery-loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function hideGalleryLoading() {
|
||||
const loading = document.querySelector('.gallery-loading');
|
||||
if (loading) {
|
||||
loading.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 갤러리 그리드 리사이즈 최적화
|
||||
let resizeTimeout;
|
||||
window.addEventListener('resize', function() {
|
||||
clearTimeout(resizeTimeout);
|
||||
resizeTimeout = setTimeout(function() {
|
||||
// 갤러리 그리드 재조정 로직
|
||||
const galleryGrid = document.querySelector('.gallery-grid');
|
||||
if (galleryGrid) {
|
||||
galleryGrid.style.opacity = '0.8';
|
||||
setTimeout(() => {
|
||||
galleryGrid.style.opacity = '1';
|
||||
}, 100);
|
||||
}
|
||||
}, 250);
|
||||
});
|
||||
|
||||
// 터치 이벤트 지원 (모바일) - 라이트박스에만 스코프 제한
|
||||
let touchStartX = 0;
|
||||
let touchEndX = 0;
|
||||
// 리스너는 한 번만 등록하고, 라이트박스 활성 상태에서만 동작
|
||||
let lightboxTouchStartX = 0;
|
||||
let lightboxTouchListenersAdded = false;
|
||||
|
||||
function handleLightboxTouchStart(e) {
|
||||
touchStartX = e.changedTouches[0].screenX;
|
||||
}
|
||||
function setupLightboxTouchListeners() {
|
||||
if (lightboxTouchListenersAdded) return;
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
if (!lightbox) return;
|
||||
lightboxTouchListenersAdded = true;
|
||||
|
||||
function handleLightboxTouchEnd(e) {
|
||||
touchEndX = e.changedTouches[0].screenX;
|
||||
const swipeThreshold = 50;
|
||||
const diff = touchStartX - touchEndX;
|
||||
lightbox.addEventListener('touchstart', function(e) {
|
||||
if (!lightbox.classList.contains('active')) return;
|
||||
lightboxTouchStartX = e.changedTouches[0].screenX;
|
||||
});
|
||||
|
||||
if (Math.abs(diff) > swipeThreshold) {
|
||||
if (diff > 0) {
|
||||
nextImage();
|
||||
} else {
|
||||
previousImage();
|
||||
lightbox.addEventListener('touchend', function(e) {
|
||||
if (!lightbox.classList.contains('active')) return;
|
||||
const touchEndX = e.changedTouches[0].screenX;
|
||||
const swipeThreshold = 50;
|
||||
const diff = lightboxTouchStartX - touchEndX;
|
||||
|
||||
if (Math.abs(diff) > swipeThreshold) {
|
||||
if (diff > 0) {
|
||||
nextImage();
|
||||
} else {
|
||||
previousImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// openLightbox/closeLightbox에서 터치 리스너 관리를 위해 원본 함수 래핑
|
||||
// openLightbox 래핑: 터치 리스너 초기화 보장
|
||||
const _originalOpenLightbox = openLightbox;
|
||||
openLightbox = function(index) {
|
||||
_originalOpenLightbox(index);
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
if (lightbox) {
|
||||
lightbox.addEventListener('touchstart', handleLightboxTouchStart);
|
||||
lightbox.addEventListener('touchend', handleLightboxTouchEnd);
|
||||
}
|
||||
setupLightboxTouchListeners();
|
||||
};
|
||||
|
||||
const _originalCloseLightbox = closeLightbox;
|
||||
closeLightbox = function() {
|
||||
const lightbox = document.getElementById('lightbox');
|
||||
if (lightbox) {
|
||||
lightbox.removeEventListener('touchstart', handleLightboxTouchStart);
|
||||
lightbox.removeEventListener('touchend', handleLightboxTouchEnd);
|
||||
}
|
||||
_originalCloseLightbox();
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 간단한 360도 파노라마 뷰어 - 좌우 스크롤 방식
|
||||
@ -378,8 +288,7 @@ class Easy360Viewer {
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
display: block;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: auto;
|
||||
filter: contrast(1.05) saturate(1.1);
|
||||
`;
|
||||
this.image.draggable = false;
|
||||
@ -408,18 +317,23 @@ class Easy360Viewer {
|
||||
// 컨테이너 크기 가져오기
|
||||
const containerHeight = this.container.clientHeight;
|
||||
const containerWidth = this.container.clientWidth;
|
||||
const imageAspectRatio = this.image.naturalWidth / this.image.naturalHeight;
|
||||
const naturalW = this.image.naturalWidth;
|
||||
const naturalH = this.image.naturalHeight;
|
||||
const imageAspectRatio = naturalW / naturalH;
|
||||
|
||||
// 모바일에서 줌 시 경계 문제 해결을 위한 더 정확한 크기 계산
|
||||
let imageHeight = containerHeight * this.zoom;
|
||||
// 컨테이너 높이에 맞추되, 원본 해상도를 초과하지 않도록 제한
|
||||
let imageHeight = Math.min(containerHeight * this.zoom, naturalH);
|
||||
let imageWidth = imageHeight * imageAspectRatio;
|
||||
|
||||
// 360도 이미지는 매우 가로가 길므로 최소 크기 보장
|
||||
const minWidth = Math.max(containerWidth * 3, imageWidth);
|
||||
imageWidth = minWidth;
|
||||
imageHeight = imageWidth / imageAspectRatio;
|
||||
// 최소 너비 보장: 컨테이너 1.5배 (3배까지 늘리면 흐릿해짐)
|
||||
// 단, 원본 너비를 초과하지 않도록 제한
|
||||
const minWidth = Math.min(containerWidth * 1.5, naturalW);
|
||||
if (imageWidth < minWidth) {
|
||||
imageWidth = minWidth;
|
||||
imageHeight = imageWidth / imageAspectRatio;
|
||||
}
|
||||
|
||||
// 줌 레벨에 따른 크기 조정 (모바일 확대 시 경계 문제 해결)
|
||||
// 줌 레벨에 따른 크기 조정
|
||||
if (this.zoom > 1) {
|
||||
imageHeight = Math.max(containerHeight, imageHeight);
|
||||
imageWidth = imageHeight * imageAspectRatio;
|
||||
|
||||
@ -81,7 +81,7 @@ function initParallaxImages() {
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
// ========================================
|
||||
@ -107,7 +107,7 @@ function initHeroScrollFade() {
|
||||
});
|
||||
ticking = true;
|
||||
}
|
||||
});
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
// ========================================
|
||||
@ -315,8 +315,9 @@ function initPortfolioTabs() {
|
||||
activePanel.classList.add('active');
|
||||
// 탭 전환 시 lazy loading 트리거
|
||||
activePanel.querySelectorAll('iframe[data-src]').forEach(iframe => {
|
||||
if (!iframe.src) {
|
||||
if (iframe.dataset.src) {
|
||||
iframe.src = iframe.dataset.src;
|
||||
iframe.removeAttribute('data-src');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,7 +80,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// ESC 키로 닫기
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
const popup = document.getElementById('mainPopup');
|
||||
if (e.key === 'Escape' && popup && popup.classList.contains('active')) {
|
||||
closePopup();
|
||||
}
|
||||
});
|
||||
|
||||
@ -322,7 +322,9 @@ function initYouTubePlayers() {
|
||||
players.push(player);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// pauseOtherVideos에서 사용할 수 있도록 전역에 저장
|
||||
window.youtubePlayers = players;
|
||||
return players;
|
||||
}
|
||||
|
||||
@ -335,7 +337,8 @@ function extractVideoId(url) {
|
||||
// 플레이어 준비 완료
|
||||
function onPlayerReady(event) {
|
||||
// 플레이어 컨테이너에 로딩 완료 클래스 추가
|
||||
const playerElement = event.target.getIframe();
|
||||
const playerElement = event.target.getIframe?.();
|
||||
if (!playerElement) return;
|
||||
const wrapper = playerElement.closest('.video-wrapper');
|
||||
if (wrapper) {
|
||||
wrapper.classList.add('loaded');
|
||||
@ -348,7 +351,8 @@ function onPlayerReady(event) {
|
||||
|
||||
// 플레이어 상태 변경
|
||||
function onPlayerStateChange(event) {
|
||||
const playerElement = event.target.getIframe();
|
||||
const playerElement = event.target.getIframe?.();
|
||||
if (!playerElement) return;
|
||||
const videoCard = playerElement.closest('.video-card');
|
||||
|
||||
if (event.data === YT.PlayerState.PLAYING) {
|
||||
@ -374,13 +378,19 @@ function onPlayerStateChange(event) {
|
||||
// 플레이어 오류 처리
|
||||
function onPlayerError(event) {
|
||||
console.error('YouTube player error:', event.data);
|
||||
const playerElement = event.target.getIframe();
|
||||
const playerElement = event.target.getIframe?.();
|
||||
if (!playerElement) return;
|
||||
const wrapper = playerElement.closest('.video-wrapper');
|
||||
|
||||
if (wrapper) {
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'video-error';
|
||||
errorDiv.innerHTML = '비디오를 로드할 수 없습니다<br><small>네트워크 연결을 확인해주세요</small>';
|
||||
const mainText = document.createElement('div');
|
||||
mainText.textContent = '비디오를 로드할 수 없습니다';
|
||||
const subText = document.createElement('small');
|
||||
subText.textContent = '네트워크 연결을 확인해주세요';
|
||||
errorDiv.appendChild(mainText);
|
||||
errorDiv.appendChild(subText);
|
||||
errorDiv.style.cssText = `
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
||||
@ -74,6 +74,8 @@
|
||||
* 이벤트 리스너 설정
|
||||
*/
|
||||
function setupEventListeners() {
|
||||
if (!elements.searchInput || !elements.imageModal) return;
|
||||
|
||||
// 검색
|
||||
elements.searchInput.addEventListener('input', debounce((e) => {
|
||||
searchQuery = e.target.value.toLowerCase();
|
||||
|
||||
19
js/qna.js
@ -143,14 +143,16 @@ function handleSearch(query) {
|
||||
return;
|
||||
}
|
||||
|
||||
const searchRegex = new RegExp(query, 'gi');
|
||||
|
||||
// 검색어 이스케이프 및 g 플래그 없이 생성 (.test()의 lastIndex 문제 방지)
|
||||
const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const searchRegex = new RegExp(escapedQuery, 'i');
|
||||
|
||||
faqItems.forEach(item => {
|
||||
const question = item.querySelector('.faq-question h3');
|
||||
const answer = item.querySelector('.faq-answer');
|
||||
const questionText = question.textContent;
|
||||
const answerText = answer.textContent;
|
||||
|
||||
|
||||
// 검색어 매칭 확인
|
||||
const questionMatch = searchRegex.test(questionText);
|
||||
const answerMatch = searchRegex.test(answerText);
|
||||
@ -162,8 +164,8 @@ function handleSearch(query) {
|
||||
// 검색어 하이라이트
|
||||
highlightSearchTerm(item, query);
|
||||
|
||||
// 답변에 매칭되는 경우 자동으로 열기
|
||||
if (answerMatch && !questionMatch) {
|
||||
// 답변에 매칭되는 경우 자동으로 열기 (이미 열려 있으면 무시)
|
||||
if (answerMatch && !questionMatch && !item.classList.contains('active')) {
|
||||
toggleFAQ(item);
|
||||
}
|
||||
} else {
|
||||
@ -303,7 +305,6 @@ function hideSuggestions() {
|
||||
|
||||
// 검색 결과 없음 표시
|
||||
function showNoResults(query) {
|
||||
const safeQuery = query.replace(/[<>&"]/g, c => ({'<':'<','>':'>','&':'&','"':'"'})[c]);
|
||||
let noResults = document.querySelector('.no-results');
|
||||
if (!noResults) {
|
||||
noResults = document.createElement('div');
|
||||
@ -311,13 +312,13 @@ function showNoResults(query) {
|
||||
noResults.innerHTML = `
|
||||
<div class="no-results-icon">🔍</div>
|
||||
<h3>검색 결과가 없습니다</h3>
|
||||
<p><strong>"${safeQuery}"</strong>와 관련된 질문을 찾을 수 없습니다.</p>
|
||||
<p><strong></strong>와 관련된 질문을 찾을 수 없습니다.</p>
|
||||
<p>다른 키워드로 검색해보시거나 <a href="contact.html">직접 문의</a>해 주세요.</p>
|
||||
`;
|
||||
document.querySelector('.faq-list').appendChild(noResults);
|
||||
} else {
|
||||
noResults.querySelector('p strong').textContent = `"${query}"`;
|
||||
}
|
||||
// textContent로 안전하게 삽입 (XSS 방지)
|
||||
noResults.querySelector('p strong').textContent = `"${query}"`;
|
||||
noResults.classList.add('active');
|
||||
}
|
||||
|
||||
|
||||
@ -659,7 +659,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
2
qna.html
@ -446,7 +446,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -760,7 +760,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-bottom">
|
||||
<p>© 2025 밍글 스튜디오. All rights reserved.</p>
|
||||
<p>© <script>document.write(new Date().getFullYear())</script> 밍글 스튜디오. All rights reserved.</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||