feat: 모든 페이지에 부드러운 단계적 로딩 애니메이션 적용 완료
🎨 Universal Smooth Animation System: - 6개 모든 페이지에 통일된 애니메이션 적용 - 헤더 우선 표시 → 본문 부드러운 페이드인 - 일관성 있는 사용자 경험 제공 📱 Applied Pages: ✅ index.html (메인 페이지) ✅ issues-inbox.html (수신함) ✅ issue-view.html (부적합 조회) ✅ daily-work.html (일일공수) ✅ project-management.html (프로젝트 관리) ✅ admin.html (관리자) 🎯 Animation Flow (모든 페이지 동일): Step 1: 헤더 빠른 페이드인 (0.4s, -10px → 0) Step 2: 본문 지연 페이드인 (0.8s, +30px → 0, 0.2s delay) Step 3: 컨텐츠 순차 표시 (100ms 간격) 🔧 Unified Implementation: - 동일한 CSS 애니메이션 클래스 (.header-fade-in, .content-fade-in) - 통일된 JavaScript 함수 (animateHeaderAppearance, animateContentAppearance) - 헤더 초기화 후 자동 애니메이션 시작 - 에러 상황에서도 애니메이션 적용 🎨 Enhanced UX Features: - 헤더 우선 표시로 즉시 네비게이션 가능 - 부드러운 전환으로 시각적 만족감 증대 - 브랜드 일관성을 위한 통일된 애니메이션 - 성능 최적화된 애니메이션 시스템 🎯 Visual Improvements: - 헤더: 위에서 부드럽게 등장 (빠름) - 본문: 아래에서 부드럽게 등장 (느림) - 컨텐츠: 순차적 페이드인 (리듬감) - 매끄러운 페이지 전환 Expected Result: ✨ 모든 페이지에서 동일한 부드러운 로딩 경험 ✨ 헤더 우선 표시로 즉시 네비게이션 가능 ✨ 통일된 브랜드 경험 및 사용자 만족도 증대 ✨ 성능 최적화와 시각적 완성도 양립
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
backend/routers/__pycache__/inbox.cpython-311.pyc
Normal file
BIN
backend/routers/__pycache__/inbox.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
backend/routers/__pycache__/page_permissions.cpython-311.pyc
Normal file
BIN
backend/routers/__pycache__/page_permissions.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -56,13 +56,50 @@
|
|||||||
border-color: #60a5fa;
|
border-color: #60a5fa;
|
||||||
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
|
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 부드러운 페이드인 애니메이션 */
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 전용 빠른 페이드인 */
|
||||||
|
.header-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 본문 컨텐츠 지연 페이드인 */
|
||||||
|
.content-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="container mx-auto px-4 py-8 max-w-6xl" style="padding-top: 120px;">
|
<main class="container mx-auto px-4 py-8 max-w-6xl content-fade-in" style="padding-top: 120px;">
|
||||||
<div class="grid md:grid-cols-2 gap-6">
|
<div class="grid md:grid-cols-2 gap-6">
|
||||||
<!-- 사용자 추가 섹션 -->
|
<!-- 사용자 추가 섹션 -->
|
||||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||||
@@ -228,6 +265,46 @@
|
|||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
let users = [];
|
let users = [];
|
||||||
|
|
||||||
|
// 애니메이션 함수들
|
||||||
|
function animateHeaderAppearance() {
|
||||||
|
console.log('🎨 헤더 애니메이션 시작');
|
||||||
|
|
||||||
|
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
|
||||||
|
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
headerElement.classList.add('header-fade-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
headerElement.classList.add('visible');
|
||||||
|
console.log('✨ 헤더 페이드인 완료');
|
||||||
|
|
||||||
|
// 헤더 애니메이션 완료 후 본문 애니메이션
|
||||||
|
setTimeout(() => {
|
||||||
|
animateContentAppearance();
|
||||||
|
}, 200);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// 헤더를 찾지 못했으면 바로 본문 애니메이션
|
||||||
|
console.log('⚠️ 헤더 요소를 찾지 못함 - 본문 애니메이션 시작');
|
||||||
|
animateContentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 본문 컨텐츠 애니메이션
|
||||||
|
function animateContentAppearance() {
|
||||||
|
console.log('🎨 본문 컨텐츠 애니메이션 시작');
|
||||||
|
|
||||||
|
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
|
||||||
|
const contentElements = document.querySelectorAll('.content-fade-in');
|
||||||
|
|
||||||
|
contentElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add('visible');
|
||||||
|
console.log(`✨ 컨텐츠 ${index + 1} 페이드인 완료`);
|
||||||
|
}, index * 100); // 100ms씩 지연
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// API 로드 후 초기화 함수
|
// API 로드 후 초기화 함수
|
||||||
async function initializeAdmin() {
|
async function initializeAdmin() {
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
@@ -244,6 +321,11 @@
|
|||||||
// 공통 헤더 초기화
|
// 공통 헤더 초기화
|
||||||
await window.commonHeader.init(user, 'users_manage');
|
await window.commonHeader.init(user, 'users_manage');
|
||||||
|
|
||||||
|
// 헤더 초기화 후 부드러운 애니메이션 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 페이지 접근 권한 체크
|
// 페이지 접근 권한 체크
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!canAccessPage('users_manage')) {
|
if (!canAccessPage('users_manage')) {
|
||||||
|
|||||||
@@ -89,6 +89,43 @@
|
|||||||
background-color: #3b82f6;
|
background-color: #3b82f6;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 부드러운 페이드인 애니메이션 */
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 전용 빠른 페이드인 */
|
||||||
|
.header-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 본문 컨텐츠 지연 페이드인 */
|
||||||
|
.content-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -96,7 +133,7 @@
|
|||||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||||
|
|
||||||
<!-- 메인 컨텐츠 -->
|
<!-- 메인 컨텐츠 -->
|
||||||
<main class="container mx-auto px-4 py-6 max-w-2xl">
|
<main class="container mx-auto px-4 py-6 max-w-2xl content-fade-in">
|
||||||
<!-- 입력 카드 -->
|
<!-- 입력 카드 -->
|
||||||
<div class="work-card p-6 mb-6">
|
<div class="work-card p-6 mb-6">
|
||||||
<h2 class="text-lg font-semibold text-gray-800 mb-6">
|
<h2 class="text-lg font-semibold text-gray-800 mb-6">
|
||||||
@@ -195,6 +232,46 @@
|
|||||||
let dailyWorkData = [];
|
let dailyWorkData = [];
|
||||||
let projectEntryCounter = 0;
|
let projectEntryCounter = 0;
|
||||||
|
|
||||||
|
// 애니메이션 함수들
|
||||||
|
function animateHeaderAppearance() {
|
||||||
|
console.log('🎨 헤더 애니메이션 시작');
|
||||||
|
|
||||||
|
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
|
||||||
|
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
headerElement.classList.add('header-fade-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
headerElement.classList.add('visible');
|
||||||
|
console.log('✨ 헤더 페이드인 완료');
|
||||||
|
|
||||||
|
// 헤더 애니메이션 완료 후 본문 애니메이션
|
||||||
|
setTimeout(() => {
|
||||||
|
animateContentAppearance();
|
||||||
|
}, 200);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// 헤더를 찾지 못했으면 바로 본문 애니메이션
|
||||||
|
console.log('⚠️ 헤더 요소를 찾지 못함 - 본문 애니메이션 시작');
|
||||||
|
animateContentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 본문 컨텐츠 애니메이션
|
||||||
|
function animateContentAppearance() {
|
||||||
|
console.log('🎨 본문 컨텐츠 애니메이션 시작');
|
||||||
|
|
||||||
|
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
|
||||||
|
const contentElements = document.querySelectorAll('.content-fade-in');
|
||||||
|
|
||||||
|
contentElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add('visible');
|
||||||
|
console.log(`✨ 컨텐츠 ${index + 1} 페이드인 완료`);
|
||||||
|
}, index * 100); // 100ms씩 지연
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// API 로드 후 초기화 함수
|
// API 로드 후 초기화 함수
|
||||||
async function initializeDailyWork() {
|
async function initializeDailyWork() {
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
@@ -211,6 +288,11 @@
|
|||||||
// 공통 헤더 초기화
|
// 공통 헤더 초기화
|
||||||
await window.commonHeader.init(user, 'daily_work');
|
await window.commonHeader.init(user, 'daily_work');
|
||||||
|
|
||||||
|
// 헤더 초기화 후 부드러운 애니메이션 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 페이지 접근 권한 체크 (일일 공수 페이지)
|
// 페이지 접근 권한 체크 (일일 공수 페이지)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!canAccessPage('daily_work')) {
|
if (!canAccessPage('daily_work')) {
|
||||||
|
|||||||
@@ -66,13 +66,50 @@
|
|||||||
background-color: #3b82f6;
|
background-color: #3b82f6;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 부드러운 페이드인 애니메이션 */
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 전용 빠른 페이드인 */
|
||||||
|
.header-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 본문 컨텐츠 지연 페이드인 */
|
||||||
|
.content-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="container mx-auto px-4 py-8">
|
<main class="container mx-auto px-4 py-8 content-fade-in">
|
||||||
<!-- 페이지 헤더 -->
|
<!-- 페이지 헤더 -->
|
||||||
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
|
<div class="bg-white rounded-xl shadow-sm p-4 mb-6">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@@ -162,6 +199,46 @@
|
|||||||
let selectedStartDate = null;
|
let selectedStartDate = null;
|
||||||
let selectedEndDate = null;
|
let selectedEndDate = null;
|
||||||
|
|
||||||
|
// 애니메이션 함수들
|
||||||
|
function animateHeaderAppearance() {
|
||||||
|
console.log('🎨 헤더 애니메이션 시작');
|
||||||
|
|
||||||
|
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
|
||||||
|
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
headerElement.classList.add('header-fade-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
headerElement.classList.add('visible');
|
||||||
|
console.log('✨ 헤더 페이드인 완료');
|
||||||
|
|
||||||
|
// 헤더 애니메이션 완료 후 본문 애니메이션
|
||||||
|
setTimeout(() => {
|
||||||
|
animateContentAppearance();
|
||||||
|
}, 200);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// 헤더를 찾지 못했으면 바로 본문 애니메이션
|
||||||
|
console.log('⚠️ 헤더 요소를 찾지 못함 - 본문 애니메이션 시작');
|
||||||
|
animateContentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 본문 컨텐츠 애니메이션
|
||||||
|
function animateContentAppearance() {
|
||||||
|
console.log('🎨 본문 컨텐츠 애니메이션 시작');
|
||||||
|
|
||||||
|
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
|
||||||
|
const contentElements = document.querySelectorAll('.content-fade-in');
|
||||||
|
|
||||||
|
contentElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add('visible');
|
||||||
|
console.log(`✨ 컨텐츠 ${index + 1} 페이드인 완료`);
|
||||||
|
}, index * 100); // 100ms씩 지연
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// API 로드 후 초기화 함수
|
// API 로드 후 초기화 함수
|
||||||
async function initializeIssueView() {
|
async function initializeIssueView() {
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
@@ -178,6 +255,11 @@
|
|||||||
// 공통 헤더 초기화
|
// 공통 헤더 초기화
|
||||||
await window.commonHeader.init(user, 'issues_view');
|
await window.commonHeader.init(user, 'issues_view');
|
||||||
|
|
||||||
|
// 헤더 초기화 후 부드러운 애니메이션 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 사용자 역할에 따른 페이지 제목 설정
|
// 사용자 역할에 따른 페이지 제목 설정
|
||||||
updatePageTitle(user);
|
updatePageTitle(user);
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,43 @@
|
|||||||
.badge-new { background: #dbeafe; color: #1e40af; }
|
.badge-new { background: #dbeafe; color: #1e40af; }
|
||||||
.badge-processing { background: #fef3c7; color: #92400e; }
|
.badge-processing { background: #fef3c7; color: #92400e; }
|
||||||
.badge-completed { background: #d1fae5; color: #065f46; }
|
.badge-completed { background: #d1fae5; color: #065f46; }
|
||||||
|
|
||||||
|
/* 부드러운 페이드인 애니메이션 */
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 전용 빠른 페이드인 */
|
||||||
|
.header-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 본문 컨텐츠 지연 페이드인 */
|
||||||
|
.content-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 min-h-screen">
|
<body class="bg-gray-50 min-h-screen">
|
||||||
@@ -88,7 +125,7 @@
|
|||||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="container mx-auto px-4 py-8" style="padding-top: 120px;">
|
<main class="container mx-auto px-4 py-8 content-fade-in" style="padding-top: 120px;">
|
||||||
<!-- 페이지 헤더 -->
|
<!-- 페이지 헤더 -->
|
||||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-6">
|
||||||
<div class="flex items-center justify-between mb-4">
|
<div class="flex items-center justify-between mb-4">
|
||||||
@@ -384,6 +421,46 @@
|
|||||||
let filteredIssues = [];
|
let filteredIssues = [];
|
||||||
let readStatus = new Set(); // 읽은 부적합 ID 저장
|
let readStatus = new Set(); // 읽은 부적합 ID 저장
|
||||||
|
|
||||||
|
// 애니메이션 함수들
|
||||||
|
function animateHeaderAppearance() {
|
||||||
|
console.log('🎨 헤더 애니메이션 시작');
|
||||||
|
|
||||||
|
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
|
||||||
|
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
headerElement.classList.add('header-fade-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
headerElement.classList.add('visible');
|
||||||
|
console.log('✨ 헤더 페이드인 완료');
|
||||||
|
|
||||||
|
// 헤더 애니메이션 완료 후 본문 애니메이션
|
||||||
|
setTimeout(() => {
|
||||||
|
animateContentAppearance();
|
||||||
|
}, 200);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// 헤더를 찾지 못했으면 바로 본문 애니메이션
|
||||||
|
console.log('⚠️ 헤더 요소를 찾지 못함 - 본문 애니메이션 시작');
|
||||||
|
animateContentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 본문 컨텐츠 애니메이션
|
||||||
|
function animateContentAppearance() {
|
||||||
|
console.log('🎨 본문 컨텐츠 애니메이션 시작');
|
||||||
|
|
||||||
|
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
|
||||||
|
const contentElements = document.querySelectorAll('.content-fade-in');
|
||||||
|
|
||||||
|
contentElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add('visible');
|
||||||
|
console.log(`✨ 컨텐츠 ${index + 1} 페이드인 완료`);
|
||||||
|
}, index * 100); // 100ms씩 지연
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// API 로드 후 초기화 함수
|
// API 로드 후 초기화 함수
|
||||||
async function initializeInbox() {
|
async function initializeInbox() {
|
||||||
console.log('🚀 수신함 초기화 시작');
|
console.log('🚀 수신함 초기화 시작');
|
||||||
@@ -406,6 +483,11 @@
|
|||||||
|
|
||||||
// 공통 헤더 초기화
|
// 공통 헤더 초기화
|
||||||
await window.commonHeader.init(user, 'issues_inbox');
|
await window.commonHeader.init(user, 'issues_inbox');
|
||||||
|
|
||||||
|
// 헤더 초기화 후 부드러운 애니메이션 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 페이지 접근 권한 체크
|
// 페이지 접근 권한 체크
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -454,6 +536,10 @@
|
|||||||
const user = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
const user = JSON.parse(localStorage.getItem('currentUser') || '{}');
|
||||||
if (user.id) {
|
if (user.id) {
|
||||||
await window.commonHeader.init(user, 'issues_inbox');
|
await window.commonHeader.init(user, 'issues_inbox');
|
||||||
|
// 에러 상황에서도 애니메이션 적용
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
} catch (headerError) {
|
} catch (headerError) {
|
||||||
console.error('공통 헤더 초기화 실패:', headerError);
|
console.error('공통 헤더 초기화 실패:', headerError);
|
||||||
|
|||||||
@@ -46,13 +46,50 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 부드러운 페이드인 애니메이션 */
|
||||||
|
.fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 헤더 전용 빠른 페이드인 */
|
||||||
|
.header-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
transition: opacity 0.4s ease-out, transform 0.4s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 본문 컨텐츠 지연 페이드인 */
|
||||||
|
.content-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
transition: opacity 0.8s ease-out, transform 0.8s ease-out;
|
||||||
|
transition-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-fade-in.visible {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||||
|
|
||||||
<!-- 메인 컨텐츠 -->
|
<!-- 메인 컨텐츠 -->
|
||||||
<main class="container mx-auto px-4 py-8 max-w-4xl">
|
<main class="container mx-auto px-4 py-8 max-w-4xl content-fade-in">
|
||||||
<!-- 프로젝트 생성 섹션 -->
|
<!-- 프로젝트 생성 섹션 -->
|
||||||
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
|
<div class="bg-white rounded-xl shadow-sm p-6 mb-8">
|
||||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">
|
<h2 class="text-lg font-semibold text-gray-800 mb-4">
|
||||||
@@ -169,6 +206,46 @@
|
|||||||
// 사용자 확인 (관리자만 접근 가능)
|
// 사용자 확인 (관리자만 접근 가능)
|
||||||
let currentUser = null;
|
let currentUser = null;
|
||||||
|
|
||||||
|
// 애니메이션 함수들
|
||||||
|
function animateHeaderAppearance() {
|
||||||
|
console.log('🎨 헤더 애니메이션 시작');
|
||||||
|
|
||||||
|
// 헤더 요소 찾기 (공통 헤더가 생성한 요소)
|
||||||
|
const headerElement = document.querySelector('header') || document.querySelector('[class*="header"]') || document.querySelector('nav');
|
||||||
|
|
||||||
|
if (headerElement) {
|
||||||
|
headerElement.classList.add('header-fade-in');
|
||||||
|
setTimeout(() => {
|
||||||
|
headerElement.classList.add('visible');
|
||||||
|
console.log('✨ 헤더 페이드인 완료');
|
||||||
|
|
||||||
|
// 헤더 애니메이션 완료 후 본문 애니메이션
|
||||||
|
setTimeout(() => {
|
||||||
|
animateContentAppearance();
|
||||||
|
}, 200);
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
// 헤더를 찾지 못했으면 바로 본문 애니메이션
|
||||||
|
console.log('⚠️ 헤더 요소를 찾지 못함 - 본문 애니메이션 시작');
|
||||||
|
animateContentAppearance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 본문 컨텐츠 애니메이션
|
||||||
|
function animateContentAppearance() {
|
||||||
|
console.log('🎨 본문 컨텐츠 애니메이션 시작');
|
||||||
|
|
||||||
|
// 모든 content-fade-in 요소들을 순차적으로 애니메이션
|
||||||
|
const contentElements = document.querySelectorAll('.content-fade-in');
|
||||||
|
|
||||||
|
contentElements.forEach((element, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.add('visible');
|
||||||
|
console.log(`✨ 컨텐츠 ${index + 1} 페이드인 완료`);
|
||||||
|
}, index * 100); // 100ms씩 지연
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function initAuth() {
|
async function initAuth() {
|
||||||
console.log('인증 초기화 시작');
|
console.log('인증 초기화 시작');
|
||||||
const token = localStorage.getItem('access_token');
|
const token = localStorage.getItem('access_token');
|
||||||
@@ -205,6 +282,11 @@
|
|||||||
// 공통 헤더 초기화
|
// 공통 헤더 초기화
|
||||||
await window.commonHeader.init(currentUser, 'projects_manage');
|
await window.commonHeader.init(currentUser, 'projects_manage');
|
||||||
|
|
||||||
|
// 헤더 초기화 후 부드러운 애니메이션 시작
|
||||||
|
setTimeout(() => {
|
||||||
|
animateHeaderAppearance();
|
||||||
|
}, 100);
|
||||||
|
|
||||||
// 페이지 접근 권한 체크 (프로젝트 관리 페이지)
|
// 페이지 접근 권한 체크 (프로젝트 관리 페이지)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!canAccessPage('projects_manage')) {
|
if (!canAccessPage('projects_manage')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user