diff --git a/css/schedule.css b/css/schedule.css index ef189d3..c425cb5 100644 --- a/css/schedule.css +++ b/css/schedule.css @@ -77,6 +77,7 @@ .cal-body { display: grid; grid-template-columns: repeat(7, 1fr); + position: relative; } /* 날짜 셀 */ @@ -302,24 +303,49 @@ font-weight: var(--font-weight-bold); } -/* 로딩 상태 - 그리드 위에 펄스 효과 */ +/* 로딩 상태 */ .cal-body.loading { position: relative; + min-height: 280px; } .cal-body.loading::after { content: ''; position: absolute; inset: 0; - background: rgba(255, 255, 255, 0.5); - animation: pulse 1.2s ease-in-out infinite; + background: rgba(255, 255, 255, 0.7); pointer-events: none; z-index: 1; } -@keyframes pulse { - 0%, 100% { opacity: 0.3; } - 50% { opacity: 0.6; } +.cal-loading-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 2; + gap: var(--spacing-md); +} + +.cal-loading-overlay .loading-spinner { + width: 36px; + height: 36px; + border: 3px solid var(--border-light); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} + +.cal-loading-overlay .loading-text { + font-size: var(--font-sm); + color: var(--text-secondary); + font-weight: var(--font-weight-medium); +} + +@keyframes spin { + to { transform: rotate(360deg); } } /* ======================================== @@ -522,7 +548,16 @@ } [data-theme="dark"] .cal-body.loading::after { - background: rgba(0, 0, 0, 0.4); + background: rgba(0, 0, 0, 0.5); +} + +[data-theme="dark"] .cal-loading-overlay .loading-spinner { + border-color: var(--glass-border); + border-top-color: var(--primary-color); +} + +[data-theme="dark"] .cal-loading-overlay .loading-text { + color: var(--dark-text-secondary); } /* ======================================== diff --git a/js/schedule.js b/js/schedule.js index 8a9e453..6d31e94 100644 --- a/js/schedule.js +++ b/js/schedule.js @@ -39,18 +39,39 @@ if (cached !== null) { buildGrid(cached); } else { - // 즉시 빈 그리드 렌더링 (체감 속도 향상) + // 즉시 빈 그리드 렌더링 + 로딩 표시 buildGrid([]); - calBody.classList.add('loading'); + showLoading(); fetchBookedDates(currentYear, currentMonth, function(dates) { setCache(currentYear, currentMonth, dates); buildGrid(dates); - calBody.classList.remove('loading'); + hideLoading(); prefetchAdjacent(); }); } } + function showLoading() { + calBody.classList.add('loading'); + // 기존 오버레이 제거 + var existing = calBody.querySelector('.cal-loading-overlay'); + if (existing) existing.remove(); + + var lang = (window.i18n && window.i18n.currentLang) || 'ko'; + var loadingTexts = { ko: '불러오는 중...', en: 'Loading...', ja: '読み込み中...', zh: '加载中...' }; + + var overlay = document.createElement('div'); + overlay.className = 'cal-loading-overlay'; + overlay.innerHTML = '
' + (loadingTexts[lang] || loadingTexts.ko) + ''; + calBody.appendChild(overlay); + } + + function hideLoading() { + calBody.classList.remove('loading'); + var overlay = calBody.querySelector('.cal-loading-overlay'); + if (overlay) overlay.remove(); + } + function prefetchAdjacent() { var nextM = currentMonth + 1, nextY = currentYear; if (nextM > 12) { nextM = 1; nextY++; }