diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx index df33301a..ef0b039a 100644 --- a/frontend/app/(auth)/login/page.tsx +++ b/frontend/app/(auth)/login/page.tsx @@ -40,7 +40,7 @@ export default function LoginPage() { const co = cosmosRef.current; if (!co) return; const cs = ["rgba(var(--v5-primary-rgb),.8)", "rgba(var(--v5-cyan-rgb),.7)", "rgba(var(--v5-pink-rgb),.7)"]; - for (let i = 0; i < 150; i++) { + for (let i = 0; i < 30; i++) { const s = document.createElement("div"); s.className = "star" + (Math.random() > 0.83 ? " c" : ""); if (s.classList.contains("c")) s.style.setProperty("--sc", cs[(Math.random() * 3) | 0]); @@ -52,7 +52,7 @@ export default function LoginPage() { co.appendChild(s); } const pc = ["var(--primary)", "var(--cyan)", "var(--pink)"]; - for (let i = 0; i < 20; i++) { + for (let i = 0; i < 0; i++) { const p = document.createElement("div"); p.className = "particle"; p.style.left = Math.random() * 100 + "%"; @@ -130,7 +130,7 @@ export default function LoginPage() {

Invy.one

-
Cosmic Command Center
+
엔터프라이즈 운영 센터에 로그인하세요
@@ -149,10 +149,10 @@ export default function LoginPage() {
-
ready to launch
+
welcome
diff --git a/frontend/app/globals.css b/frontend/app/globals.css index a44c94d9..04be0a67 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -863,7 +863,7 @@ select { /* ease-in-out-cubic — 대칭형 곡선으로 시간/progress 가 거의 linear 에 가깝게 진행됨. duration 은 themeTransition.ts 가 방향에 따라 --vt-duration 변수로 지정. 검정→대비 강해서 빨라 보이는 perceived speed 비대칭을 보정하기 위해 dark 방향은 더 김. */ - animation: vt-soft-reveal var(--vt-duration, 1800ms) cubic-bezier(0.65, 0, 0.35, 1) forwards; + animation: vt-soft-reveal var(--vt-duration, 500ms) cubic-bezier(0.65, 0, 0.35, 1) forwards; } @keyframes vt-soft-reveal { diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx index 0326afe5..688ab6eb 100644 --- a/frontend/components/layout/AppLayout.tsx +++ b/frontend/components/layout/AppLayout.tsx @@ -539,119 +539,40 @@ function AppLayoutInner({ children }: AppLayoutProps) { toast.warning("이 메뉴에 할당된 화면이 없습니다. 메뉴 설정을 확인해주세요."); }; - const handleModeSwitch = useCallback((e?: React.MouseEvent) => { + const handleModeSwitch = useCallback((_e?: React.MouseEvent) => { if (modeTransition !== "idle") return; - // 강화된 mode transition — 옵션 b/c/e/f 적용 (d 버튼 burst 제거, mode-fade overlay 도 제거): - // Phase 1 (0ms): 사이드바 items morph-out (stagger), 헤더 glow flash, - // breadcrumb swap-out, 이탈 시 admin badge zoom-out - // Phase 2 (350ms): React 가 모드 swap → 새 메뉴 morph-in (stagger), breadcrumb swap-in, - // 진입 시 admin badge zoom-in - // Phase 3 (~950ms): 모든 클래스 정리, idle 복귀 - const goingToAdmin = !isAdminMode; + // Simplified mode transition — sidebar items stagger morph only, no burst/sweep/badge theatrics. + // Phase 1 (0ms): sidebar items morph-out (stagger 20ms) + // Phase 2 (180ms): React swaps mode, new items morph-in (stagger 20ms) + // Phase 3 (~400ms): cleanup → idle setModeTransition("out"); - // (b) 사이드바 items morph-out — stagger + // sidebar items morph-out (stagger 20ms) const oldItems = Array.from(document.querySelectorAll(".v5-side .v5-si")); oldItems.forEach((it, i) => { - it.style.animationDelay = `${i * 35}ms`; + it.style.animationDelay = `${i * 20}ms`; it.classList.add("mode-morph-out"); }); - // (c) 헤더 glow line flash - const hdrGlow = document.querySelector(".v5-hdr-glow"); - if (hdrGlow) { - hdrGlow.classList.remove("mode-flash"); - void hdrGlow.offsetWidth; // reflow → 재시작 가능하게 - hdrGlow.classList.add("mode-flash"); - } - - // (d) 토글 버튼 burst — 디자인시스템 mode-burst 포팅 - // 클릭 좌표에 ring 1개 + radial particle 10개. admin 진입은 cyan, 사용자 복귀는 primary. - const targetEl = e?.currentTarget as HTMLElement | undefined; - const rect = targetEl?.getBoundingClientRect(); - const bx = rect ? rect.left + rect.width / 2 : (e?.clientX ?? window.innerWidth - 80); - const by = rect ? rect.top + rect.height / 2 : (e?.clientY ?? 25); - const burst = document.createElement("div"); - burst.className = `v5-mode-burst${goingToAdmin ? " admin" : ""}`; - burst.style.left = `${bx}px`; - burst.style.top = `${by}px`; - const ring = document.createElement("span"); - ring.className = "burst-ring"; - burst.appendChild(ring); - const N = 10; - for (let i = 0; i < N; i++) { - const p = document.createElement("span"); - p.className = "burst-particle"; - const angle = (i / N) * Math.PI * 2; - const dist = 36 + Math.random() * 22; - p.style.setProperty("--tx", `${Math.cos(angle) * dist}px`); - p.style.setProperty("--ty", `${Math.sin(angle) * dist}px`); - p.style.animationDelay = `${i * 8}ms`; - burst.appendChild(p); - } - document.body.appendChild(burst); - setTimeout(() => burst.remove(), 1100); - - // (d2) 헤더 하단 좌→우 sweep (admin 진입은 cyan→primary→pink, 사용자 복귀는 primary) - const hdrEl = document.querySelector(".v5-hdr"); - if (hdrEl) { - const sweep = document.createElement("div"); - sweep.className = "v5-mode-sweep"; - sweep.setAttribute("data-mode", goingToAdmin ? "admin" : "user"); - hdrEl.appendChild(sweep); - setTimeout(() => sweep.remove(), 900); - } - - // (e) breadcrumb swap-out - const bc = document.querySelector(".v5-hdr-bc"); - bc?.classList.remove("mode-swap-in"); - bc?.classList.add("mode-swap-out"); - - // (f) admin badge zoom-out (이탈 시에만) - if (!goingToAdmin) { - const badge = document.querySelector(".v5-admin-badge"); - badge?.classList.remove("mode-zoom-in"); - badge?.classList.add("mode-zoom-out"); - } - setTimeout(() => { - // Phase 2: 모드 swap → React 재렌더 (새 메뉴/탭/breadcrumb) setTabMode(isAdminMode ? "user" : "admin"); setModeTransition("in"); - - // 다음 프레임에 새 items 에 morph-in 적용 requestAnimationFrame(() => { const newItems = Array.from(document.querySelectorAll(".v5-side .v5-si")); newItems.forEach((it, i) => { - it.style.animationDelay = `${i * 45}ms`; + it.style.animationDelay = `${i * 20}ms`; it.classList.add("mode-morph-in"); }); - - // breadcrumb swap-in (텍스트는 이미 새 모드 기준) - const newBc = document.querySelector(".v5-hdr-bc"); - newBc?.classList.remove("mode-swap-out"); - newBc?.classList.add("mode-swap-in"); - - // (f) admin badge zoom-in (진입 시에만) - if (goingToAdmin) { - const newBadge = document.querySelector(".v5-admin-badge"); - newBadge?.classList.remove("mode-zoom-out"); - newBadge?.classList.add("mode-zoom-in"); - } }); - - // Phase 3: 모든 애니메이션 클래스 정리 setTimeout(() => { setModeTransition("idle"); document.querySelectorAll(".v5-side .v5-si").forEach((it) => { it.classList.remove("mode-morph-in", "mode-morph-out"); it.style.animationDelay = ""; }); - document.querySelector(".v5-hdr-bc")?.classList.remove("mode-swap-in", "mode-swap-out"); - document.querySelector(".v5-admin-badge")?.classList.remove("mode-zoom-in", "mode-zoom-out"); - }, 600); - }, 350); + }, 300); + }, 180); }, [isAdminMode, setTabMode, modeTransition]); const handleLogout = async () => { @@ -788,7 +709,7 @@ function AppLayoutInner({ children }: AppLayoutProps) { }} > {menu.icon} - {menu.name} + {menu.name?.trim() || "(이름 없음)"} {menu.hasChildren && !sidebarCollapsed && ( {isExpanded ? : } @@ -998,6 +919,8 @@ function AppLayoutInner({ children }: AppLayoutProps) { )} +