diff --git a/frontend/app/(auth)/login/page.tsx b/frontend/app/(auth)/login/page.tsx
index 16b52fdf..e2b348b8 100644
--- a/frontend/app/(auth)/login/page.tsx
+++ b/frontend/app/(auth)/login/page.tsx
@@ -129,7 +129,7 @@ export default function LoginPage() {
diff --git a/frontend/app/(main)/page.tsx b/frontend/app/(main)/page.tsx
index b7befe6a..a21a9d31 100644
--- a/frontend/app/(main)/page.tsx
+++ b/frontend/app/(main)/page.tsx
@@ -63,7 +63,7 @@ export default function MainHomePage() {
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
index 7fb69e1d..c884bd10 100644
--- a/frontend/app/globals.css
+++ b/frontend/app/globals.css
@@ -855,8 +855,9 @@ select {
mask-repeat: repeat, no-repeat;
mask-position: 0 0, 0 0;
/* ease-in-out-cubic — 대칭형 곡선으로 시간/progress 가 거의 linear 에 가깝게 진행됨.
- 1800ms 중 약 1260ms 가 시각적 reveal 에 쓰임 — 빠르지도 늦지도 않은 sweet spot. */
- animation: vt-soft-reveal 1800ms cubic-bezier(0.65, 0, 0.35, 1) forwards;
+ 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;
}
@keyframes vt-soft-reveal {
diff --git a/frontend/components/admin/CompanySwitcher.tsx b/frontend/components/admin/CompanySwitcher.tsx
index e7890897..2643de95 100644
--- a/frontend/components/admin/CompanySwitcher.tsx
+++ b/frontend/components/admin/CompanySwitcher.tsx
@@ -20,9 +20,9 @@ interface CompanySwitcherProps {
}
/**
- * WACE 관리자 전용: 회사 선택 및 전환 컴포넌트
+ * Invyone 관리자 전용: 회사 선택 및 전환 컴포넌트
*
- * - WACE 관리자(company_code = "*", userType = "SUPER_ADMIN")만 표시
+ * - Invyone 관리자(company_code = "*", userType = "SUPER_ADMIN")만 표시
* - 회사 선택 시 해당 회사로 전환하여 시스템 사용
* - JWT 토큰 재발급으로 company_code 변경
*/
@@ -33,15 +33,15 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(false);
- // WACE 관리자 권한 체크 (user_type만 확인)
- const isWaceAdmin = user?.user_type === "SUPER_ADMIN";
+ // Invyone 관리자 권한 체크 (user_type만 확인)
+ const isInvyoneAdmin = user?.user_type === "SUPER_ADMIN";
// 현재 선택된 회사명 표시
const currentCompanyName = React.useMemo(() => {
if (!user?.company_code) return "로딩 중...";
if (user.company_code === "*") {
- return "WACE (최고 관리자)";
+ return "Invyone (최고 관리자)";
}
// companies 배열에서 현재 회사 찾기
@@ -51,10 +51,10 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
// 회사 목록 조회
useEffect(() => {
- if (isWaceAdmin && isOpen) {
+ if (isInvyoneAdmin && isOpen) {
fetchCompanies();
}
- }, [isWaceAdmin, isOpen]);
+ }, [isInvyoneAdmin, isOpen]);
// 검색 필터링
useEffect(() => {
@@ -75,24 +75,24 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
const response = await apiClient.get("/admin/companies/db");
if (response.data.success) {
- // 활성 상태의 회사만 필터링 + company_code="*" 제외 (WACE는 별도 추가)
+ // 활성 상태의 회사만 필터링 + company_code="*" 제외 (Invyone는 별도 추가)
const activeCompanies = response.data.data
.filter((c: Company) => c.company_code !== "*") // DB의 "*" 제외
.filter((c: Company) => c.status === "active" || !c.status)
.sort((a: Company, b: Company) => a.company_name.localeCompare(b.company_name));
- // WACE 복귀 옵션 추가
- const companiesWithWace: Company[] = [
+ // Invyone 복귀 옵션 추가
+ const companiesWithInvyone: Company[] = [
{
company_code: "*",
- company_name: "WACE (최고 관리자)",
+ company_name: "Invyone (최고 관리자)",
status: "active",
},
...activeCompanies,
];
- setCompanies(companiesWithWace);
- setFilteredCompanies(companiesWithWace);
+ setCompanies(companiesWithInvyone);
+ setFilteredCompanies(companiesWithInvyone);
}
} catch (error) {
logger.error("회사 목록 조회 실패", error);
@@ -124,8 +124,8 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
}
};
- // WACE 관리자가 아니면 렌더링하지 않음
- if (!isWaceAdmin) {
+ // Invyone 관리자가 아니면 렌더링하지 않음
+ if (!isInvyoneAdmin) {
return null;
}
diff --git a/frontend/components/layout/AppLayout.tsx b/frontend/components/layout/AppLayout.tsx
index cbd8a30d..a2e3f6fd 100644
--- a/frontend/components/layout/AppLayout.tsx
+++ b/frontend/components/layout/AppLayout.tsx
@@ -281,7 +281,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
const companyCode = (user as ExtendedUserInfo)?.company_code;
if (companyCode === "*") {
- setCurrentCompanyName("WACE (최고 관리자)");
+ setCurrentCompanyName("Invyone (최고 관리자)");
} else if (companyCode) {
try {
const response = await apiClient.get("/admin/companies/db");
@@ -455,28 +455,84 @@ function AppLayoutInner({ children }: AppLayoutProps) {
toast.warning("이 메뉴에 할당된 화면이 없습니다. 메뉴 설정을 확인해주세요.");
};
- const handleModeSwitch = useCallback(() => {
+ const handleModeSwitch = useCallback((e?: React.MouseEvent
) => {
if (modeTransition !== "idle") return;
- // 레퍼런스 invion-layout-v5.html switchMode() 를 그대로 따름:
- // Phase 1 (0ms) : mode-fade 오버레이 페이드인 + 사이드바 slide-out + 탭 fade-out
- // Phase 2 (300ms) : 모드 swap (React 재렌더) → 새 사이드바/탭 등장
- // Phase 3 (600ms) : 오버레이 페이드아웃 + 정리
- const fade = document.getElementById("v5-mode-fade");
+ // 강화된 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;
setModeTransition("out");
- fade?.classList.add("in");
+
+ // (b) 사이드바 items morph-out — stagger
+ const oldItems = Array.from(document.querySelectorAll(".v5-side .v5-si"));
+ oldItems.forEach((it, i) => {
+ it.style.animationDelay = `${i * 35}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 효과는 제거됨 — 가운데 밝아지는 게 거슬려서 통째로 뺌
+
+ // (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 mode — React re-renders with new menus/tabs
+ // Phase 2: 모드 swap → React 재렌더 (새 메뉴/탭/breadcrumb)
setTabMode(isAdminMode ? "user" : "admin");
setModeTransition("in");
- // Phase 3: 오버레이 페이드아웃 후 정리
+ // 다음 프레임에 새 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.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(() => {
- fade?.classList.remove("in");
setModeTransition("idle");
- }, 300);
- }, 300);
+ 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);
}, [isAdminMode, setTabMode, modeTransition]);
const handleLogout = async () => {
@@ -727,9 +783,6 @@ function AppLayoutInner({ children }: AppLayoutProps) {
{/* Theme fade overlay */}
- {/* Mode transition fade overlay — radial gradient 으로 화면을 한 번 덮어 사이드바/탭 swap 을 가림 */}
-
-
{/* V5 Shell */}
{/* ===== Glass Header ===== */}
@@ -739,7 +792,7 @@ function AppLayoutInner({ children }: AppLayoutProps) {
-
INVION
+
Invy.one
{isAdminMode ? "관리자" : "홈"} › {breadcrumbText}
@@ -748,6 +801,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
관리자 모드
+ {/* mode transition 헤더 glow 라인 — 평소엔 opacity 0, mode change 시에만 flash */}
+