Initial commit: 교육 프로젝트 배포
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
/**
|
||||
* intro-animation.js
|
||||
* 인트로 페이지 애니메이션 함수들
|
||||
* 공통 모듈 활용 (ErrorHandler, DOMUtils, Utils, AnimationUtils)
|
||||
*/
|
||||
|
||||
// 전역 의존성 (폴백 포함)
|
||||
const _domUtils = typeof DOMUtils !== 'undefined' ? DOMUtils : null;
|
||||
const _errorHandler = typeof ErrorHandler !== 'undefined' ? ErrorHandler : null;
|
||||
const _utils = typeof Utils !== 'undefined' ? Utils : null;
|
||||
const _animationUtils = typeof AnimationUtils !== 'undefined' ? AnimationUtils : null;
|
||||
|
||||
/**
|
||||
* 에러 처리 헬퍼
|
||||
* @private
|
||||
*/
|
||||
function _handleError(error, context, additionalInfo = {}) {
|
||||
if (_errorHandler) {
|
||||
_errorHandler.handle(error, {
|
||||
context: `IntroAnimation.${context}`,
|
||||
component: 'IntroAnimation',
|
||||
...additionalInfo
|
||||
}, false);
|
||||
} else {
|
||||
console.error(`[IntroAnimation] ${context}:`, error, additionalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 1: 이름 타이핑 애니메이션
|
||||
*/
|
||||
function typeName() {
|
||||
try {
|
||||
// 안전성 검사
|
||||
if (typeof INTRO_CONFIG === 'undefined' || typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_CONFIG or INTRO_STATE is not defined'), 'typeName');
|
||||
return;
|
||||
}
|
||||
|
||||
const typedNameEl = _domUtils?.$("#typedName") || document.getElementById("typedName");
|
||||
const cursorEl = _domUtils?.$("#cursor") || document.getElementById("cursor");
|
||||
|
||||
if (!typedNameEl) {
|
||||
_handleError(new Error('typedName element not found'), 'typeName');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!INTRO_CONFIG.fullName || typeof INTRO_CONFIG.fullName !== 'string' || INTRO_CONFIG.fullName.length === 0) {
|
||||
_handleError(new Error('INTRO_CONFIG.fullName is empty or invalid'), 'typeName');
|
||||
return;
|
||||
}
|
||||
|
||||
if (INTRO_STATE.typedIndex < INTRO_CONFIG.fullName.length) {
|
||||
const currentText = INTRO_CONFIG.fullName.slice(0, INTRO_STATE.typedIndex + 1);
|
||||
typedNameEl.textContent = currentText;
|
||||
INTRO_STATE.typedIndex++;
|
||||
|
||||
const delay = INTRO_CONFIG.typingSpeed || 100;
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(delay).then(() => typeName());
|
||||
} else {
|
||||
setTimeout(typeName, delay);
|
||||
}
|
||||
} else {
|
||||
if (cursorEl) {
|
||||
if (_domUtils && _domUtils.setStyles) {
|
||||
_domUtils.setStyles(cursorEl, { opacity: '0' });
|
||||
} else {
|
||||
cursorEl.style.opacity = "0";
|
||||
}
|
||||
}
|
||||
|
||||
const delay = 600;
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(delay).then(() => animateWelcome());
|
||||
} else {
|
||||
setTimeout(animateWelcome, delay);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'typeName');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 1: 환영 메시지 애니메이션
|
||||
*/
|
||||
function animateWelcome() {
|
||||
try {
|
||||
if (typeof INTRO_CONFIG === 'undefined' || typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_CONFIG or INTRO_STATE is not defined'), 'animateWelcome');
|
||||
return;
|
||||
}
|
||||
|
||||
const container = _domUtils?.$("#welcome") || document.getElementById("welcome");
|
||||
if (!container) {
|
||||
_handleError(new Error('welcome element not found'), 'animateWelcome');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!INTRO_CONFIG.welcomeText || typeof INTRO_CONFIG.welcomeText !== 'string') {
|
||||
_handleError(new Error('INTRO_CONFIG.welcomeText is empty or invalid'), 'animateWelcome');
|
||||
return;
|
||||
}
|
||||
|
||||
const tempDiv = _domUtils?.createElement('div') || document.createElement("div");
|
||||
tempDiv.innerHTML = INTRO_CONFIG.welcomeText;
|
||||
|
||||
let html = "";
|
||||
tempDiv.childNodes.forEach((node) => {
|
||||
try {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
const text = node.textContent || '';
|
||||
html += text
|
||||
.split("")
|
||||
.map((c) => {
|
||||
const escaped = c === " " ? " " : (c === "<" ? "<" : c === ">" ? ">" : c === "&" ? "&" : c);
|
||||
return `<span>${escaped}</span>`;
|
||||
})
|
||||
.join("");
|
||||
} else if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const innerText = node.textContent || '';
|
||||
const tagName = node.tagName.toLowerCase();
|
||||
const escapedText = innerText
|
||||
.split("")
|
||||
.map((c) => {
|
||||
const escaped = c === " " ? " " : (c === "<" ? "<" : c === ">" ? ">" : c === "&" ? "&" : c);
|
||||
return `<span>${escaped}</span>`;
|
||||
})
|
||||
.join("");
|
||||
html += `<${tagName}>${escapedText}</${tagName}>`;
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'animateWelcome.processNode', { node });
|
||||
}
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
const spans = container.querySelectorAll("span");
|
||||
const charSpeed = INTRO_CONFIG.welcomeCharSpeed || 50;
|
||||
|
||||
spans.forEach((char, i) => {
|
||||
const delay = i * charSpeed;
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(delay).then(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(char, 'show');
|
||||
} else {
|
||||
char.classList.add("show");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(char, 'show');
|
||||
} else {
|
||||
char.classList.add("show");
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
});
|
||||
|
||||
// 애니메이션 완료 후 자동 스크롤 타이머 시작
|
||||
const fullNameLength = INTRO_CONFIG.fullName ? INTRO_CONFIG.fullName.length : 0;
|
||||
const totalDelay = fullNameLength * charSpeed + 1000;
|
||||
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(totalDelay).then(() => startAutoScrollTimer());
|
||||
} else {
|
||||
setTimeout(() => startAutoScrollTimer(), totalDelay);
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'animateWelcome');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 2: 업데이트 텍스트 + 카드 애니메이션
|
||||
* - 모바일(≤992px): section2Text 애니메이션 → 페이드아웃 → card-list 표시
|
||||
* - 데스크탑(>992px): section2Text + card-list 동시 표시
|
||||
*/
|
||||
function animateSection2() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE is not defined'), 'animateSection2');
|
||||
return;
|
||||
}
|
||||
|
||||
const isMobile = window.innerWidth <= 992;
|
||||
const lines = ["line1", "line2", "line3"];
|
||||
const cards = ["card1", "card2", "card3", "card4"];
|
||||
const lineDelay = 1200;
|
||||
const cardDelay = isMobile ? 750 : 500;
|
||||
const totalLineDelay = lines.length * lineDelay + 300;
|
||||
const delayFn = (_utils && _utils.delay)
|
||||
? (ms) => _utils.delay(ms)
|
||||
: (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
// 라인 순차 애니메이션
|
||||
lines.forEach((id, i) => {
|
||||
const element = _domUtils?.$(`#${id}`) || document.getElementById(id);
|
||||
if (!element) {
|
||||
console.warn(`[IntroAnimation] Element #${id} not found`);
|
||||
return;
|
||||
}
|
||||
delayFn(i * lineDelay).then(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.add("show");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 카드 순차 애니메이션 헬퍼
|
||||
const showCards = () => {
|
||||
cards.forEach((id, i) => {
|
||||
const cardElement = _domUtils?.$(`#${id}`) || document.getElementById(id);
|
||||
if (!cardElement) {
|
||||
console.warn(`[IntroAnimation] Element #${id} not found`);
|
||||
return;
|
||||
}
|
||||
delayFn(i * cardDelay).then(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(cardElement, 'show');
|
||||
} else {
|
||||
cardElement.classList.add("show");
|
||||
}
|
||||
if (i === 3) {
|
||||
INTRO_STATE.section2AnimDone = true;
|
||||
startAutoScrollTimer();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
delayFn(totalLineDelay).then(() => {
|
||||
if (isMobile) {
|
||||
// 모바일: section2Text 페이드아웃 → cardsContainer 표시 → 카드 애니메이션
|
||||
const section2Text = _domUtils?.$("#section2Text") || document.getElementById("section2Text");
|
||||
const cardsContainer = _domUtils?.$("#cardsContainer") || document.getElementById("cardsContainer");
|
||||
|
||||
if (section2Text) {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(section2Text, 'fade-out');
|
||||
} else {
|
||||
section2Text.classList.add("fade-out");
|
||||
}
|
||||
}
|
||||
|
||||
delayFn(800).then(() => {
|
||||
if (section2Text) {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(section2Text, 'hidden');
|
||||
_domUtils.removeClasses(section2Text, 'fade-out');
|
||||
} else {
|
||||
section2Text.classList.add("hidden");
|
||||
section2Text.classList.remove("fade-out");
|
||||
}
|
||||
}
|
||||
|
||||
// 카드 표시 시 스크롤 인디케이터 숨기기
|
||||
const scrollIndicator = _domUtils?.$("#scrollIndicator") || document.getElementById("scrollIndicator");
|
||||
if (scrollIndicator) {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(scrollIndicator, 'hidden');
|
||||
} else {
|
||||
scrollIndicator.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
if (cardsContainer) {
|
||||
if (_domUtils && _domUtils.setStyles) {
|
||||
_domUtils.setStyles(cardsContainer, { display: 'flex' });
|
||||
} else {
|
||||
cardsContainer.style.display = "flex";
|
||||
}
|
||||
}
|
||||
showCards();
|
||||
});
|
||||
} else {
|
||||
// 데스크탑: 바로 카드 표시
|
||||
showCards();
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
_handleError(error, 'animateSection2');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 2: 애니메이션 리셋
|
||||
*/
|
||||
function resetSection2() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE is not defined'), 'resetSection2');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = ["line1", "line2", "line3"];
|
||||
const cards = ["card1", "card2", "card3", "card4"];
|
||||
|
||||
lines.forEach((id) => {
|
||||
const element = _domUtils?.$(`#${id}`) || document.getElementById(id);
|
||||
if (element) {
|
||||
if (_domUtils && _domUtils.removeClasses) {
|
||||
_domUtils.removeClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cards.forEach((id) => {
|
||||
const element = _domUtils?.$(`#${id}`) || document.getElementById(id);
|
||||
if (element) {
|
||||
if (_domUtils && _domUtils.removeClasses) {
|
||||
_domUtils.removeClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
INTRO_STATE.section2AnimDone = false;
|
||||
} catch (error) {
|
||||
_handleError(error, 'resetSection2');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 3: CTA 애니메이션
|
||||
*/
|
||||
function animateSection3() {
|
||||
try {
|
||||
const ctaBtn = _domUtils?.$("#ctaBtn") || document.getElementById("ctaBtn");
|
||||
const ctaLine1 = _domUtils?.$("#ctaLine1") || document.getElementById("ctaLine1");
|
||||
const ctaLine2 = _domUtils?.$("#ctaLine2") || document.getElementById("ctaLine2");
|
||||
|
||||
const addShowClass = (element, delay) => {
|
||||
if (!element) {
|
||||
console.warn(`[IntroAnimation] Element not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(delay).then(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.add("show");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (_domUtils && _domUtils.addClasses) {
|
||||
_domUtils.addClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.add("show");
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
};
|
||||
|
||||
addShowClass(ctaBtn, 100);
|
||||
addShowClass(ctaLine1, 300);
|
||||
addShowClass(ctaLine2, 500);
|
||||
|
||||
// 모바일: 마스크 중앙에서 전체 화면으로 자동 확장 (CTA 섹션 진입 후 약 1초 뒤)
|
||||
if (window.innerWidth <= 992) {
|
||||
const maskContainer = _domUtils?.$("#maskContainer") || document.getElementById("maskContainer");
|
||||
if (maskContainer && typeof INTRO_STATE !== "undefined") {
|
||||
if (INTRO_STATE.mobileMaskExpandTimer) {
|
||||
clearTimeout(INTRO_STATE.mobileMaskExpandTimer);
|
||||
INTRO_STATE.mobileMaskExpandTimer = null;
|
||||
}
|
||||
const mobileMaskDelayMs = 1500;
|
||||
const runExpand = () => {
|
||||
INTRO_STATE.mobileMaskExpandTimer = null;
|
||||
if (INTRO_STATE.currentSection === 2) {
|
||||
maskContainer.classList.add("expand");
|
||||
}
|
||||
};
|
||||
INTRO_STATE.mobileMaskExpandTimer = setTimeout(runExpand, mobileMaskDelayMs);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'animateSection3');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 3: 애니메이션 리셋
|
||||
*/
|
||||
function resetSection3() {
|
||||
try {
|
||||
const ctaLine1 = _domUtils?.$("#ctaLine1") || document.getElementById("ctaLine1");
|
||||
const ctaLine2 = _domUtils?.$("#ctaLine2") || document.getElementById("ctaLine2");
|
||||
const ctaBtn = _domUtils?.$("#ctaBtn") || document.getElementById("ctaBtn");
|
||||
|
||||
const removeShowClass = (element) => {
|
||||
if (element) {
|
||||
if (_domUtils && _domUtils.removeClasses) {
|
||||
_domUtils.removeClasses(element, 'show');
|
||||
} else {
|
||||
element.classList.remove("show");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
removeShowClass(ctaLine1);
|
||||
removeShowClass(ctaLine2);
|
||||
removeShowClass(ctaBtn);
|
||||
|
||||
// 모바일: 마스크 확장 리셋 · 진행 중인 자동 확장 타이머 취소
|
||||
if (typeof INTRO_STATE !== "undefined" && INTRO_STATE.mobileMaskExpandTimer) {
|
||||
clearTimeout(INTRO_STATE.mobileMaskExpandTimer);
|
||||
INTRO_STATE.mobileMaskExpandTimer = null;
|
||||
}
|
||||
const maskContainer = _domUtils?.$("#maskContainer") || document.getElementById("maskContainer");
|
||||
if (maskContainer) {
|
||||
maskContainer.classList.remove("expand");
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'resetSection3');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 자동 스크롤 타이머 시작
|
||||
*/
|
||||
function startAutoScrollTimer() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE is not defined'), 'startAutoScrollTimer');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof handleScrollDown !== 'function') {
|
||||
_handleError(new Error('handleScrollDown function is not defined'), 'startAutoScrollTimer');
|
||||
return;
|
||||
}
|
||||
|
||||
// 기존 타이머 정리
|
||||
if (INTRO_STATE.autoScrollTimer) {
|
||||
clearTimeout(INTRO_STATE.autoScrollTimer);
|
||||
INTRO_STATE.autoScrollTimer = null;
|
||||
}
|
||||
|
||||
// 섹션 0: 1500ms, 섹션 1(업데이트+카드): 6000ms, 그 외: 3000ms
|
||||
const delay =
|
||||
INTRO_STATE.currentSection === 0
|
||||
? 1500
|
||||
: INTRO_STATE.currentSection === 1
|
||||
? 6000
|
||||
: 3000;
|
||||
|
||||
if (_utils && _utils.delay) {
|
||||
_utils.delay(delay).then(() => {
|
||||
// 마지막 상호작용 후 설정된 시간이 지났는지 확인
|
||||
const now = Date.now();
|
||||
const lastInteraction = INTRO_STATE.lastInteractionTime || 0;
|
||||
|
||||
if (now - lastInteraction >= delay) {
|
||||
try {
|
||||
handleScrollDown();
|
||||
} catch (error) {
|
||||
_handleError(error, 'startAutoScrollTimer.handleScrollDown');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
INTRO_STATE.autoScrollTimer = setTimeout(() => {
|
||||
try {
|
||||
// 마지막 상호작용 후 설정된 시간이 지났는지 확인
|
||||
const now = Date.now();
|
||||
const lastInteraction = INTRO_STATE.lastInteractionTime || 0;
|
||||
|
||||
if (now - lastInteraction >= delay) {
|
||||
handleScrollDown();
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'startAutoScrollTimer.handleScrollDown');
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'startAutoScrollTimer');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 사용자 상호작용 감지 - 자동 스크롤 타이머 리셋
|
||||
*/
|
||||
function resetAutoScrollTimer() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE is not defined'), 'resetAutoScrollTimer');
|
||||
return;
|
||||
}
|
||||
|
||||
INTRO_STATE.lastInteractionTime = Date.now();
|
||||
startAutoScrollTimer();
|
||||
} catch (error) {
|
||||
_handleError(error, 'resetAutoScrollTimer');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* intro-events.js
|
||||
* 이벤트 리스너 설정
|
||||
*/
|
||||
|
||||
/**
|
||||
* 모든 이벤트 리스너 등록
|
||||
*/
|
||||
function initEventListeners() {
|
||||
// 마우스 휠 이벤트
|
||||
document.addEventListener("wheel", (e) => {
|
||||
resetAutoScrollTimer();
|
||||
if (e.deltaY > 0) handleScrollDown();
|
||||
else handleScrollUp();
|
||||
});
|
||||
|
||||
// 터치 이벤트
|
||||
let touchStartY = 0;
|
||||
document.addEventListener(
|
||||
"touchstart",
|
||||
(e) => {
|
||||
touchStartY = e.touches[0].clientY;
|
||||
resetAutoScrollTimer();
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
document.addEventListener(
|
||||
"touchend",
|
||||
(e) => {
|
||||
const diff = touchStartY - e.changedTouches[0].clientY;
|
||||
if (Math.abs(diff) > 60) {
|
||||
if (diff > 0) handleScrollDown();
|
||||
else handleScrollUp();
|
||||
}
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
// 키보드 이벤트
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (["ArrowDown", "PageDown", " "].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
resetAutoScrollTimer();
|
||||
handleScrollDown();
|
||||
} else if (["ArrowUp", "PageUp"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
resetAutoScrollTimer();
|
||||
handleScrollUp();
|
||||
}
|
||||
});
|
||||
|
||||
// 마우스 움직임 이벤트 (Section 3 마스크 효과)
|
||||
// requestAnimationFrame을 사용하여 부드러운 업데이트 보장
|
||||
let rafId = null;
|
||||
let lastMouseEvent = null;
|
||||
|
||||
document.addEventListener("mousemove", (e) => {
|
||||
if (typeof INTRO_STATE !== 'undefined' && INTRO_STATE.currentSection === 2) {
|
||||
// 마지막 마우스 이벤트 저장
|
||||
lastMouseEvent = e;
|
||||
|
||||
// 이미 요청된 애니메이션 프레임이 없으면 새로 요청
|
||||
if (rafId === null) {
|
||||
rafId = requestAnimationFrame(() => {
|
||||
if (lastMouseEvent) {
|
||||
handleMouseMoveOnSection3(lastMouseEvent);
|
||||
}
|
||||
rafId = null;
|
||||
lastMouseEvent = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Section 3의 마우스 움직임 처리 (마스크 효과)
|
||||
* @param {MouseEvent} e - 마우스 이벤트
|
||||
*/
|
||||
function handleMouseMoveOnSection3(e) {
|
||||
try {
|
||||
// 입력 검증
|
||||
if (!e || typeof e.clientX !== 'number' || typeof e.clientY !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
const maskContainer = document.getElementById("maskContainer");
|
||||
if (!maskContainer) {
|
||||
return; // 요소가 없으면 조용히 종료
|
||||
}
|
||||
|
||||
const mouseX = e.clientX;
|
||||
const mouseY = e.clientY;
|
||||
|
||||
const mainContainer = document.getElementById("mainContainer");
|
||||
if (!mainContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainContainerRect = mainContainer.getBoundingClientRect();
|
||||
const h1Elements = mainContainer.querySelectorAll("p");
|
||||
const ctaBtn = document.querySelector(".cta-btn");
|
||||
let hovering = false;
|
||||
let isCtaBtnHovering = false;
|
||||
|
||||
// .cta-btn 위에 마우스가 있는지 먼저 체크
|
||||
if (ctaBtn) {
|
||||
try {
|
||||
const ctaRect = ctaBtn.getBoundingClientRect();
|
||||
if (
|
||||
mouseX >= ctaRect.left &&
|
||||
mouseX <= ctaRect.right &&
|
||||
mouseY >= ctaRect.top &&
|
||||
mouseY <= ctaRect.bottom
|
||||
) {
|
||||
hovering = true;
|
||||
isCtaBtnHovering = true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[IntroEvents] CTA 버튼 체크 중 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// p 요소들 체크
|
||||
try {
|
||||
h1Elements.forEach((element) => {
|
||||
try {
|
||||
const h1Rect = element.getBoundingClientRect();
|
||||
if (
|
||||
mouseX >= h1Rect.left &&
|
||||
mouseX <= h1Rect.right &&
|
||||
mouseY >= h1Rect.top &&
|
||||
mouseY <= h1Rect.bottom
|
||||
) {
|
||||
hovering = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// 개별 요소 체크 실패는 무시
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn('[IntroEvents] 요소 체크 중 오류:', error);
|
||||
}
|
||||
|
||||
// .cta-btn 위에 있을 때만 세로 위치 고정, 그 외에는 마우스 위치 따라감
|
||||
const targetY = isCtaBtnHovering
|
||||
? mainContainerRect.top + mainContainerRect.height / 2.3
|
||||
: mouseY;
|
||||
|
||||
// CSS 변수 설정 (안전하게)
|
||||
const x = Math.max(0, Math.min(mouseX, window.innerWidth));
|
||||
const y = Math.max(0, Math.min(targetY, window.innerHeight));
|
||||
const targetSize = hovering ? 380 : 30;
|
||||
|
||||
maskContainer.style.setProperty("--x", `${x}px`);
|
||||
maskContainer.style.setProperty("--y", `${y}px`);
|
||||
maskContainer.style.setProperty("--size", `${targetSize}px`);
|
||||
} catch (error) {
|
||||
console.error('[IntroEvents] handleMouseMoveOnSection3 오류:', error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
/**
|
||||
* intro-init.js
|
||||
* 인트로 페이지 초기화
|
||||
* 공통 모듈 활용 (ErrorHandler, DOMUtils, Utils, EventManager)
|
||||
*/
|
||||
|
||||
// 전역 의존성 (폴백 포함)
|
||||
const _initDomUtils = typeof DOMUtils !== 'undefined' ? DOMUtils : null;
|
||||
const _initErrorHandler = typeof ErrorHandler !== 'undefined' ? ErrorHandler : null;
|
||||
const _initUtils = typeof Utils !== 'undefined' ? Utils : null;
|
||||
const _initEventManager = typeof eventManager !== 'undefined' ? eventManager : null;
|
||||
|
||||
/**
|
||||
* 에러 처리 헬퍼
|
||||
* @private
|
||||
*/
|
||||
function _handleError(error, context, additionalInfo = {}) {
|
||||
if (_initErrorHandler) {
|
||||
_initErrorHandler.handle(error, {
|
||||
context: `IntroInit.${context}`,
|
||||
component: 'IntroInit',
|
||||
...additionalInfo
|
||||
}, false);
|
||||
} else {
|
||||
console.error(`[IntroInit] ${context}:`, error, additionalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 페이지 초기 설정
|
||||
*/
|
||||
function initializeIntroPage() {
|
||||
try {
|
||||
// DOM 요소 선택
|
||||
const cardsContainer = _initDomUtils?.$("#cardsContainer") || document.getElementById("cardsContainer");
|
||||
const ctaBtn = _initDomUtils?.$("#ctaBtn") || document.getElementById("ctaBtn");
|
||||
const maskContainer = _initDomUtils?.$("#maskContainer") || document.getElementById("maskContainer");
|
||||
const typedNameEl = _initDomUtils?.$("#typedName") || document.getElementById("typedName");
|
||||
const cursorEl = _initDomUtils?.$("#cursor") || document.getElementById("cursor");
|
||||
|
||||
// 초기 요소 숨김 처리
|
||||
if (cardsContainer) {
|
||||
if (_initDomUtils && _initDomUtils.setStyles) {
|
||||
_initDomUtils.setStyles(cardsContainer, { display: 'none' });
|
||||
} else {
|
||||
cardsContainer.style.display = "none";
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] cardsContainer 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (ctaBtn) {
|
||||
if (_initDomUtils && _initDomUtils.setStyles) {
|
||||
_initDomUtils.setStyles(ctaBtn, { display: 'none' });
|
||||
} else {
|
||||
ctaBtn.style.display = "none";
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] ctaBtn 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (maskContainer) {
|
||||
maskContainer.style.setProperty("--size", "30px");
|
||||
} else {
|
||||
console.warn('[IntroInit] maskContainer 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 이벤트 리스너 등록
|
||||
if (typeof initEventListeners === 'function') {
|
||||
try {
|
||||
initEventListeners();
|
||||
} catch (error) {
|
||||
_handleError(error, 'initializeIntroPage.initEventListeners');
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] initEventListeners 함수를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// typedIndex 초기화 및 typedName 요소 초기화
|
||||
if (typeof INTRO_STATE !== 'undefined') {
|
||||
INTRO_STATE.typedIndex = 0;
|
||||
} else {
|
||||
console.warn('[IntroInit] INTRO_STATE가 정의되지 않았습니다.');
|
||||
}
|
||||
|
||||
if (typedNameEl) {
|
||||
typedNameEl.textContent = "";
|
||||
} else {
|
||||
console.warn('[IntroInit] typedName 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (cursorEl) {
|
||||
if (_initDomUtils && _initDomUtils.setStyles) {
|
||||
_initDomUtils.setStyles(cursorEl, { opacity: '1' });
|
||||
} else {
|
||||
cursorEl.style.opacity = "1";
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] cursor 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 타이핑 애니메이션 시작 (INTRO_CONFIG가 준비된 후)
|
||||
const animationDelay = 800;
|
||||
const delayFn = _initUtils && _initUtils.delay ? _initUtils.delay : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
delayFn(animationDelay).then(() => {
|
||||
try {
|
||||
if (typeof INTRO_CONFIG !== 'undefined' && typeof INTRO_STATE !== 'undefined' && INTRO_CONFIG.fullName) {
|
||||
// text-ani 섹션 표시 (CSS에서 display: none이므로 animating 클래스 추가)
|
||||
const textAniSection = _initDomUtils?.$('.text-ani') || document.querySelector('.text-ani');
|
||||
if (textAniSection) {
|
||||
if (_initDomUtils && _initDomUtils.addClasses) {
|
||||
_initDomUtils.addClasses(textAniSection, 'animating');
|
||||
} else {
|
||||
textAniSection.classList.add('animating');
|
||||
}
|
||||
}
|
||||
|
||||
INTRO_STATE.typedIndex = 0;
|
||||
|
||||
if (typeof typeName === 'function') {
|
||||
typeName();
|
||||
} else {
|
||||
console.warn('[IntroInit] typeName 함수를 찾을 수 없습니다.');
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] INTRO_CONFIG 또는 INTRO_STATE가 준비되지 않았거나 fullName이 없습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'initializeIntroPage.animationStart');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
_handleError(error, 'initializeIntroPage');
|
||||
}
|
||||
}
|
||||
|
||||
// DOM이 로드되면 초기화 실행
|
||||
function setupInitialization() {
|
||||
try {
|
||||
if (document.readyState === "loading") {
|
||||
// EventManager를 사용하여 이벤트 등록 (폴백 포함)
|
||||
if (_initEventManager) {
|
||||
_initEventManager.once(document, "DOMContentLoaded", initializeIntroPage);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", initializeIntroPage, { once: true });
|
||||
}
|
||||
} else {
|
||||
// DOMContentLoaded 이벤트가 이미 발생한 경우
|
||||
initializeIntroPage();
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'setupInitialization');
|
||||
// 폴백: 에러가 발생해도 초기화 시도
|
||||
if (document.readyState !== "loading") {
|
||||
initializeIntroPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupInitialization();
|
||||
|
||||
/**
|
||||
* 외부에서 사용자 이름을 설정하고 다시 시작하는 함수
|
||||
* @param {string} name - 사용자 이름
|
||||
*/
|
||||
function restartIntroWithName(name) {
|
||||
try {
|
||||
// 입력 검증
|
||||
if (typeof name !== 'string' || name.trim() === '') {
|
||||
_handleError(new Error('유효하지 않은 사용자 이름'), 'restartIntroWithName', { name });
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE가 정의되지 않았습니다'), 'restartIntroWithName');
|
||||
return;
|
||||
}
|
||||
|
||||
// 이름 설정
|
||||
if (typeof setUserName === 'function') {
|
||||
try {
|
||||
setUserName(name);
|
||||
} catch (error) {
|
||||
_handleError(error, 'restartIntroWithName.setUserName');
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] setUserName 함수를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 상태 초기화
|
||||
INTRO_STATE.currentSection = 0;
|
||||
INTRO_STATE.typedIndex = 0;
|
||||
INTRO_STATE.isScrolling = false;
|
||||
INTRO_STATE.isAnimating = false;
|
||||
INTRO_STATE.section2AnimDone = false;
|
||||
clearTimeout(INTRO_STATE.autoScrollTimer);
|
||||
INTRO_STATE.lastInteractionTime = Date.now();
|
||||
|
||||
// DOM 요소 선택
|
||||
const typedNameEl = _initDomUtils?.$("#typedName") || document.getElementById("typedName");
|
||||
const cursorEl = _initDomUtils?.$("#cursor") || document.getElementById("cursor");
|
||||
const welcomeEl = _initDomUtils?.$("#welcome") || document.getElementById("welcome");
|
||||
const textAniSection = _initDomUtils?.$('.text-ani') || document.querySelector('.text-ani');
|
||||
|
||||
// 화면 리셋
|
||||
if (typedNameEl) {
|
||||
typedNameEl.textContent = "";
|
||||
} else {
|
||||
console.warn('[IntroInit] typedName 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (cursorEl) {
|
||||
if (_initDomUtils && _initDomUtils.setStyles) {
|
||||
_initDomUtils.setStyles(cursorEl, { opacity: '1' });
|
||||
} else {
|
||||
cursorEl.style.opacity = "1";
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] cursor 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
if (welcomeEl) {
|
||||
welcomeEl.innerHTML = "";
|
||||
} else {
|
||||
console.warn('[IntroInit] welcome 요소를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// text-ani 섹션 표시
|
||||
if (textAniSection) {
|
||||
if (_initDomUtils && _initDomUtils.addClasses) {
|
||||
_initDomUtils.addClasses(textAniSection, 'animating');
|
||||
} else {
|
||||
textAniSection.classList.add('animating');
|
||||
}
|
||||
}
|
||||
|
||||
// 섹션 1로 이동
|
||||
if (typeof goToSection === 'function') {
|
||||
try {
|
||||
goToSection(0);
|
||||
} catch (error) {
|
||||
_handleError(error, 'restartIntroWithName.goToSection');
|
||||
}
|
||||
} else {
|
||||
console.warn('[IntroInit] goToSection 함수를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 타이핑 재시작
|
||||
const animationDelay = 800;
|
||||
const delayFn = _initUtils && _initUtils.delay ? _initUtils.delay : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
delayFn(animationDelay).then(() => {
|
||||
try {
|
||||
if (typeof typeName === 'function') {
|
||||
typeName();
|
||||
} else {
|
||||
console.warn('[IntroInit] typeName 함수를 찾을 수 없습니다.');
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'restartIntroWithName.typeName');
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
_handleError(error, 'restartIntroWithName');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* intro-section.js
|
||||
* 섹션 전환 및 스크롤 처리 로직
|
||||
* 공통 모듈 활용 (ErrorHandler, DOMUtils, AnimationUtils, Utils)
|
||||
*/
|
||||
|
||||
// 전역 의존성 (폴백 포함)
|
||||
const _sectionDomUtils = typeof DOMUtils !== 'undefined' ? DOMUtils : null;
|
||||
const _sectionErrorHandler = typeof ErrorHandler !== 'undefined' ? ErrorHandler : null;
|
||||
const _sectionUtils = typeof Utils !== 'undefined' ? Utils : null;
|
||||
const _sectionAnimationUtils = typeof AnimationUtils !== 'undefined' ? AnimationUtils : null;
|
||||
|
||||
/**
|
||||
* 에러 처리 헬퍼
|
||||
* @private
|
||||
*/
|
||||
function _handleError(error, context, additionalInfo = {}) {
|
||||
if (_sectionErrorHandler) {
|
||||
_sectionErrorHandler.handle(error, {
|
||||
context: `IntroSection.${context}`,
|
||||
component: 'IntroSection',
|
||||
...additionalInfo
|
||||
}, false);
|
||||
} else {
|
||||
console.error(`[IntroSection] ${context}:`, error, additionalInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 섹션으로 이동
|
||||
* @param {number} index - 이동할 섹션 인덱스 (0, 1, 2)
|
||||
*/
|
||||
function goToSection(index) {
|
||||
try {
|
||||
// 입력 검증
|
||||
if (typeof index !== 'number' || index < 0 || index > 2) {
|
||||
_handleError(new Error(`유효하지 않은 섹션 인덱스: ${index}`), 'goToSection');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE가 정의되지 않았습니다'), 'goToSection');
|
||||
return;
|
||||
}
|
||||
|
||||
if (INTRO_STATE.isAnimating) return;
|
||||
INTRO_STATE.isAnimating = true;
|
||||
|
||||
// 자동 스크롤 타이머 정지
|
||||
clearTimeout(INTRO_STATE.autoScrollTimer);
|
||||
|
||||
// DOM 요소 선택
|
||||
const section1Text = _sectionDomUtils?.$("#section1Text") || document.getElementById("section1Text");
|
||||
const section2Text = _sectionDomUtils?.$("#section2Text") || document.getElementById("section2Text");
|
||||
const section3Text = _sectionDomUtils?.$("#section3Text") || document.getElementById("section3Text");
|
||||
const cardsContainer = _sectionDomUtils?.$("#cardsContainer") || document.getElementById("cardsContainer");
|
||||
const ctaBtn = _sectionDomUtils?.$("#ctaBtn") || document.getElementById("ctaBtn");
|
||||
const scrollIndicator = _sectionDomUtils?.$("#scrollIndicator") || document.getElementById("scrollIndicator");
|
||||
|
||||
// 요소 존재 확인
|
||||
if (!section1Text || !section2Text || !section3Text || !cardsContainer || !ctaBtn || !scrollIndicator) {
|
||||
_handleError(new Error('필수 DOM 요소를 찾을 수 없습니다'), 'goToSection');
|
||||
INTRO_STATE.isAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 현재 섹션 페이드 아웃
|
||||
const currentSection = INTRO_STATE.currentSection;
|
||||
if (currentSection === 0) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.addClasses) {
|
||||
_sectionDomUtils.addClasses(section1Text, 'fade-out');
|
||||
} else {
|
||||
section1Text.classList.add("fade-out");
|
||||
}
|
||||
} else if (currentSection === 1) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.addClasses) {
|
||||
_sectionDomUtils.addClasses(section2Text, 'fade-out');
|
||||
} else {
|
||||
section2Text.classList.add("fade-out");
|
||||
}
|
||||
|
||||
// 카드 숨기기
|
||||
["card1", "card2", "card3", "card4"].forEach((id) => {
|
||||
const card = _sectionDomUtils?.$(`#${id}`) || document.getElementById(id);
|
||||
if (card) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.removeClasses) {
|
||||
_sectionDomUtils.removeClasses(card, 'show');
|
||||
} else {
|
||||
card.classList.remove("show");
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (currentSection === 2) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.addClasses) {
|
||||
_sectionDomUtils.addClasses(section3Text, 'fade-out');
|
||||
} else {
|
||||
section3Text.classList.add("fade-out");
|
||||
}
|
||||
if (_sectionDomUtils && _sectionDomUtils.removeClasses) {
|
||||
_sectionDomUtils.removeClasses(ctaBtn, 'show');
|
||||
} else {
|
||||
ctaBtn.classList.remove("show");
|
||||
}
|
||||
}
|
||||
|
||||
// 전환 애니메이션 지연
|
||||
const transitionDelay = 400;
|
||||
const delayFn = _sectionUtils && _sectionUtils.delay ? _sectionUtils.delay : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
delayFn(transitionDelay).then(() => {
|
||||
try {
|
||||
// 모든 섹션 숨기기
|
||||
if (_sectionDomUtils && _sectionDomUtils.addClasses) {
|
||||
_sectionDomUtils.addClasses(section1Text, 'hidden');
|
||||
_sectionDomUtils.addClasses(section2Text, 'hidden');
|
||||
_sectionDomUtils.addClasses(section3Text, 'hidden');
|
||||
} else {
|
||||
section1Text.classList.add("hidden");
|
||||
section2Text.classList.add("hidden");
|
||||
section3Text.classList.add("hidden");
|
||||
}
|
||||
|
||||
if (_sectionDomUtils && _sectionDomUtils.removeClasses) {
|
||||
_sectionDomUtils.removeClasses(scrollIndicator, 'sec2');
|
||||
_sectionDomUtils.removeClasses(section1Text, 'fade-out');
|
||||
_sectionDomUtils.removeClasses(section2Text, 'fade-out');
|
||||
_sectionDomUtils.removeClasses(section3Text, 'fade-out');
|
||||
} else {
|
||||
scrollIndicator.classList.remove("sec2");
|
||||
section1Text.classList.remove("fade-out");
|
||||
section2Text.classList.remove("fade-out");
|
||||
section3Text.classList.remove("fade-out");
|
||||
}
|
||||
|
||||
// 목표 섹션 표시
|
||||
if (index === 0) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.removeClasses) {
|
||||
_sectionDomUtils.removeClasses(section1Text, 'hidden');
|
||||
_sectionDomUtils.removeClasses(scrollIndicator, 'hidden');
|
||||
} else {
|
||||
section1Text.classList.remove("hidden");
|
||||
scrollIndicator.classList.remove("hidden");
|
||||
}
|
||||
|
||||
if (_sectionDomUtils && _sectionDomUtils.setStyles) {
|
||||
_sectionDomUtils.setStyles(cardsContainer, { display: 'none' });
|
||||
_sectionDomUtils.setStyles(ctaBtn, { display: 'none' });
|
||||
} else {
|
||||
cardsContainer.style.display = "none";
|
||||
ctaBtn.style.display = "none";
|
||||
}
|
||||
} else if (index === 1) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.addClasses) {
|
||||
_sectionDomUtils.addClasses(scrollIndicator, 'sec2');
|
||||
_sectionDomUtils.removeClasses(section2Text, 'hidden');
|
||||
_sectionDomUtils.removeClasses(scrollIndicator, 'hidden');
|
||||
} else {
|
||||
scrollIndicator.classList.add("sec2");
|
||||
section2Text.classList.remove("hidden");
|
||||
scrollIndicator.classList.remove("hidden");
|
||||
}
|
||||
|
||||
// 모바일(≤992px): section2Text 먼저 보여준 뒤 animateSection2에서 카드 표시
|
||||
const isMobile = window.innerWidth <= 992;
|
||||
if (_sectionDomUtils && _sectionDomUtils.setStyles) {
|
||||
_sectionDomUtils.setStyles(cardsContainer, { display: isMobile ? 'none' : 'flex' });
|
||||
_sectionDomUtils.setStyles(ctaBtn, { display: 'none' });
|
||||
} else {
|
||||
cardsContainer.style.display = isMobile ? "none" : "flex";
|
||||
ctaBtn.style.display = "none";
|
||||
}
|
||||
|
||||
resetSection2();
|
||||
setTimeout(animateSection2, 200);
|
||||
} else if (index === 2) {
|
||||
if (_sectionDomUtils && _sectionDomUtils.removeClasses) {
|
||||
_sectionDomUtils.removeClasses(section3Text, 'hidden');
|
||||
_sectionDomUtils.addClasses(scrollIndicator, 'hidden');
|
||||
_sectionDomUtils.removeClasses(scrollIndicator, 'sec2');
|
||||
} else {
|
||||
section3Text.classList.remove("hidden");
|
||||
scrollIndicator.classList.add("hidden");
|
||||
scrollIndicator.classList.remove("sec2");
|
||||
}
|
||||
|
||||
if (_sectionDomUtils && _sectionDomUtils.setStyles) {
|
||||
_sectionDomUtils.setStyles(cardsContainer, { display: 'none' });
|
||||
_sectionDomUtils.setStyles(ctaBtn, { display: 'block' });
|
||||
} else {
|
||||
cardsContainer.style.display = "none";
|
||||
ctaBtn.style.display = "block";
|
||||
}
|
||||
|
||||
resetSection3();
|
||||
setTimeout(animateSection3, 200);
|
||||
}
|
||||
|
||||
INTRO_STATE.currentSection = index;
|
||||
|
||||
// 애니메이션 완료 플래그 리셋
|
||||
const resetDelay = 500;
|
||||
delayFn(resetDelay).then(() => {
|
||||
INTRO_STATE.isAnimating = false;
|
||||
});
|
||||
} catch (error) {
|
||||
_handleError(error, 'goToSection.transition');
|
||||
INTRO_STATE.isAnimating = false;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
_handleError(error, 'goToSection');
|
||||
if (typeof INTRO_STATE !== 'undefined') {
|
||||
INTRO_STATE.isAnimating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 다음 섹션으로 스크롤
|
||||
*/
|
||||
function handleScrollDown() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE가 정의되지 않았습니다'), 'handleScrollDown');
|
||||
return;
|
||||
}
|
||||
|
||||
if (INTRO_STATE.isScrolling || INTRO_STATE.isAnimating) return;
|
||||
|
||||
const delayFn = _sectionUtils && _sectionUtils.delay ? _sectionUtils.delay : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const scrollCooldown = 700;
|
||||
|
||||
if (INTRO_STATE.currentSection === 0) {
|
||||
INTRO_STATE.isScrolling = true;
|
||||
delayFn(scrollCooldown).then(() => {
|
||||
INTRO_STATE.isScrolling = false;
|
||||
});
|
||||
goToSection(1);
|
||||
} else if (INTRO_STATE.currentSection === 1 && INTRO_STATE.section2AnimDone) {
|
||||
INTRO_STATE.isScrolling = true;
|
||||
delayFn(scrollCooldown).then(() => {
|
||||
INTRO_STATE.isScrolling = false;
|
||||
});
|
||||
goToSection(2);
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'handleScrollDown');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이전 섹션으로 스크롤
|
||||
*/
|
||||
function handleScrollUp() {
|
||||
try {
|
||||
if (typeof INTRO_STATE === 'undefined') {
|
||||
_handleError(new Error('INTRO_STATE가 정의되지 않았습니다'), 'handleScrollUp');
|
||||
return;
|
||||
}
|
||||
|
||||
if (INTRO_STATE.isScrolling || INTRO_STATE.isAnimating) return;
|
||||
|
||||
INTRO_STATE.isScrolling = true;
|
||||
const delayFn = _sectionUtils && _sectionUtils.delay ? _sectionUtils.delay : (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const scrollCooldown = 700;
|
||||
|
||||
delayFn(scrollCooldown).then(() => {
|
||||
INTRO_STATE.isScrolling = false;
|
||||
});
|
||||
|
||||
if (INTRO_STATE.currentSection === 2) {
|
||||
goToSection(1);
|
||||
} else if (INTRO_STATE.currentSection === 1) {
|
||||
goToSection(0);
|
||||
}
|
||||
} catch (error) {
|
||||
_handleError(error, 'handleScrollUp');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user