Compare commits

2 Commits

Author SHA1 Message Date
Claude (gbpark) 3f481acd8e UI Phase A — 컴포넌트 크기/폰트 상향, UPPERCASE 제거
Build & Deploy to K8s / build-and-deploy (push) Successful in 3m59s
- v5-btn 30→34px, sm 26→28px, lg 40px 추가. 폰트 11.2→13px
- v5-bdg UPPERCASE/letter-spacing 제거 (한글 자간 왜곡 해결), 9.28→11.5px
- v5-tbl 헤더 UPPERCASE 제거, font-weight 600, 폰트 9.6→13px, 행 padding 증가
- v5-kpi-num 1.85→2rem (display 토큰 정렬), delta/sub 9.9→12px
- v5-card-title UPPERCASE 제거, 10.5→13px
- v5-crumbs 9.6→11.5px, 몬오스페이스 제거
- v5-page-sub 10.9→13px
- v5-modal max-width 420→560, size 변형 추가 (sm 420, lg 720, xl 960)
- v5-modal-body body-sm→body 토큰
- 사이드바 .v5-si 폰트 12.3→13px, 여백 증가
- 헤더 breadcrumb 11.5→13px

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:02:45 +09:00
Claude (gbpark) 407da15e6d UI 디자인 패스 — 타이포/헤더/빈화면/로그인 정돈
- 타이포 스케일: body 12→14px, caption 9.6→12px, display 25.6→32px, 위계 강화
- 헤더 우측 3그룹화 (대시보드액션 | 테마/알림/설정 | 모드+프로필), v5-hdr-sep 구분자 추가
- 사이드바 SUPER_ADMIN 회사 카드 borderless slim 라벨로 압축
- 메뉴명 빈 텍스트 방어 + title 속성 추가
- 빈 대시보드(EmptyDashboard) 리디자인: 탭없음/위젯없음 2상태 분리, 2-CTA 카드
- 로그인 코스믹 공연 축소: 별 150→30, 파티클 20→0, 카피 한글화 (로그인 버튼/서브타이틀)
- 모드 전환 burst/sweep/badge-zoom 제거, sidebar stagger morph만 유지 (handleModeSwitch 100→25줄)
- View transitions duration 1800ms → 500ms

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:42:46 +09:00
6 changed files with 144 additions and 191 deletions
+5 -5
View File
@@ -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() {
</div>
<div className="logo"><h1>Invy.one</h1></div>
<div className="login-sub">Cosmic Command Center</div>
<div className="login-sub"> </div>
<form onSubmit={handleLogin}>
<div className="fg">
@@ -149,10 +149,10 @@ export default function LoginPage() {
</div>
</div>
<div className="login-divider"><span>ready to launch</span></div>
<div className="login-divider"><span>welcome</span></div>
<button type="submit" className="lbtn" disabled={isLoading} onMouseDown={handleRipple}>
{isLoading ? <span className="spinner" /> : <><span>Launch</span><ArrowRight width={16} height={16} /></>}
{isLoading ? <span className="spinner" /> : <><span></span><ArrowRight width={16} height={16} /></>}
</button>
</form>
+1 -1
View File
@@ -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 {
+22 -100
View File
@@ -539,119 +539,40 @@ function AppLayoutInner({ children }: AppLayoutProps) {
toast.warning("이 메뉴에 할당된 화면이 없습니다. 메뉴 설정을 확인해주세요.");
};
const handleModeSwitch = useCallback((e?: React.MouseEvent<HTMLButtonElement>) => {
const handleModeSwitch = useCallback((_e?: React.MouseEvent<HTMLButtonElement>) => {
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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".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<HTMLElement>(".v5-admin-badge");
newBadge?.classList.remove("mode-zoom-out");
newBadge?.classList.add("mode-zoom-in");
}
});
// Phase 3: 모든 애니메이션 클래스 정리
setTimeout(() => {
setModeTransition("idle");
document.querySelectorAll<HTMLElement>(".v5-side .v5-si").forEach((it) => {
it.classList.remove("mode-morph-in", "mode-morph-out");
it.style.animationDelay = "";
});
document.querySelector<HTMLElement>(".v5-hdr-bc")?.classList.remove("mode-swap-in", "mode-swap-out");
document.querySelector<HTMLElement>(".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) {
}}
>
<span className="ic">{menu.icon}</span>
<span className="truncate">{menu.name}</span>
<span className="truncate" title={menu.name || ""}>{menu.name?.trim() || "(이름 없음)"}</span>
{menu.hasChildren && !sidebarCollapsed && (
<span className="ml-auto">
{isExpanded ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
@@ -998,6 +919,8 @@ function AppLayoutInner({ children }: AppLayoutProps) {
</button>
)}
<span className="v5-hdr-sep" aria-hidden="true" />
{/* Theme toggle — single sun/moon icon (디자인시스템 준수, 2026-04-21) */}
<button
className="v5-hdr-icon"
@@ -1036,7 +959,9 @@ function AppLayoutInner({ children }: AppLayoutProps) {
<SlidersHorizontal size={16} />
</button>
{/* Admin toggle (gear ↔ home) — 이벤트 전달해서 burst origin 으로 사용 */}
{isAdmin && <span className="v5-hdr-sep" aria-hidden="true" />}
{/* Admin toggle (gear ↔ home) */}
{isAdmin && (
<button
className="v5-hdr-icon v5-mode-toggle"
@@ -1111,16 +1036,13 @@ function AppLayoutInner({ children }: AppLayoutProps) {
<aside className={`v5-side v5-side-anim ${isAdminMode ? "v5-admin-side" : ""} ${sidebarCollapsed ? "collapsed" : ""} ${isMobile ? (sidebarOpen ? "mobile-open" : "") : ""} ${modeTransition === "out" ? "slide-out" : modeTransition === "in" ? "slide-in" : ""}`}
style={isMobile ? { position: "fixed", left: 0, top: 0, bottom: 0, zIndex: 30, paddingTop: "60px", width: "260px", transform: sidebarOpen ? "none" : "translateX(-100%)" } : undefined}
>
{/* SUPER_ADMIN company info */}
{/* SUPER_ADMIN company info (slim, borderless) */}
{(user as ExtendedUserInfo)?.user_type === "SUPER_ADMIN" && !sidebarCollapsed && (
<div style={{ padding: ".5rem .6rem", marginBottom: ".25rem" }}>
<div className="flex items-center gap-2 rounded-lg p-2" style={{ background: "var(--v5-surface)", border: "1px solid var(--v5-glass-border)", borderRadius: "10px", fontSize: ".7rem" }}>
<Building2 size={14} style={{ color: "var(--v5-primary)", flexShrink: 0 }} />
<div className="min-w-0 flex-1">
<p style={{ fontSize: ".55rem", color: "var(--v5-text-muted)" }}> </p>
<p className="truncate font-semibold" style={{ fontSize: ".72rem", color: "var(--v5-text)" }}>{currentCompanyName || "로딩 중..."}</p>
</div>
</div>
<div className="flex items-center gap-2 px-2.5 py-1.5" title={currentCompanyName || ""}>
<Building2 size={12} style={{ color: "var(--v5-primary)", flexShrink: 0 }} />
<p className="truncate font-medium min-w-0 flex-1" style={{ fontSize: ".78rem", color: "var(--v5-text)" }}>
{currentCompanyName || "로딩 중..."}
</p>
</div>
)}
+55 -33
View File
@@ -1,6 +1,6 @@
"use client";
import { LayoutGrid, Plus } from "lucide-react";
import { LayoutGrid, Plus, Sparkles } from "lucide-react";
import { useDashboardStore } from "@/stores/dashboardStore";
export function EmptyDashboard() {
@@ -8,50 +8,72 @@ export function EmptyDashboard() {
const openLib = useDashboardStore((s) => s.openLib);
const hasDashboard = !!activeDashboardId;
const handleCenterClick = () => {
if (hasDashboard) openLib();
};
// 탭이 없는 상태: 메뉴 선택 유도
if (!hasDashboard) {
return (
<div className="flex h-full items-center justify-center px-6">
<div className="flex max-w-md flex-col items-center gap-4 text-center">
<div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-[var(--v5-primary)]/8 text-[var(--v5-primary)]">
<LayoutGrid className="h-7 w-7" />
</div>
<div className="space-y-1.5">
<h2 className="text-[1.125rem] font-semibold text-foreground"> </h2>
<p className="text-sm text-muted-foreground">
.
</p>
</div>
</div>
</div>
);
}
// 대시보드는 있으나 위젯이 없는 상태: 3가지 CTA 제시
return (
<div
className={`flex h-full items-center justify-center bg-white dark:bg-transparent ${hasDashboard ? "cursor-pointer" : ""}`}
onClick={handleCenterClick}
role={hasDashboard ? "button" : undefined}
tabIndex={hasDashboard ? 0 : undefined}
onKeyDown={(e) => {
if (hasDashboard && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
openLib();
}
}}
>
<div className="flex flex-col items-center gap-4 text-center transition-transform hover:scale-[1.02]">
<div className="flex h-20 w-20 items-center justify-center rounded-full bg-[var(--v5-primary)]/10 text-[var(--v5-primary)] ring-1 ring-[var(--v5-primary)]/30">
{hasDashboard ? <Plus className="h-10 w-10" /> : <LayoutGrid className="h-10 w-10 text-muted-foreground" />}
<div className="flex h-full items-center justify-center bg-white px-6 dark:bg-transparent">
<div className="flex w-full max-w-xl flex-col items-center gap-6 text-center">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[var(--v5-primary)]/20 to-[var(--v5-cyan)]/15 text-[var(--v5-primary)] ring-1 ring-[var(--v5-primary)]/25">
<Sparkles className="h-8 w-8" />
</div>
<div className="space-y-2">
<h2 className="text-xl font-semibold text-foreground">
{hasDashboard ? "이제 템플릿을 추가합니다" : "열린 탭이 없습니다"}
<h2 className="text-[1.25rem] font-semibold text-foreground">
</h2>
<p className="max-w-sm text-sm text-muted-foreground">
{hasDashboard
? "이 영역을 클릭하거나 상단의 '템플릿 추가' 버튼을 눌러 첫 카드를 배치하세요."
: "왼쪽 사이드바에서 메뉴를 클릭하거나 드래그하여 탭을 추가하세요."}
릿 , .
</p>
</div>
{hasDashboard && (
<div className="grid w-full grid-cols-1 gap-3 sm:grid-cols-2">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
openLib();
}}
className="mt-1 inline-flex items-center gap-1.5 rounded-md bg-[var(--v5-primary)] px-3 py-1.5 text-[0.7rem] font-semibold text-white hover:opacity-90"
onClick={openLib}
className="group flex flex-col items-start gap-1.5 rounded-xl border border-[var(--v5-primary)]/25 bg-[var(--v5-primary)]/[0.04] px-4 py-3.5 text-left transition-all hover:border-[var(--v5-primary)]/60 hover:bg-[var(--v5-primary)]/[0.08] hover:-translate-y-0.5"
>
<Plus className="h-3.5 w-3.5" />
릿
<div className="flex items-center gap-2 text-[var(--v5-primary)]">
<LayoutGrid className="h-4 w-4" />
<span className="text-sm font-semibold">릿 </span>
</div>
<p className="text-xs text-muted-foreground">
</p>
</button>
)}
<button
type="button"
onClick={openLib}
className="group flex flex-col items-start gap-1.5 rounded-xl border border-border bg-card px-4 py-3.5 text-left transition-all hover:border-[var(--v5-primary)]/50 hover:-translate-y-0.5"
>
<div className="flex items-center gap-2 text-foreground">
<Plus className="h-4 w-4" />
<span className="text-sm font-semibold"> </span>
</div>
<p className="text-xs text-muted-foreground">
</p>
</button>
</div>
<p className="text-[0.7rem] text-muted-foreground/70">
: 상단의 &apos;&apos; .
</p>
</div>
</div>
);
+46 -40
View File
@@ -11,10 +11,10 @@
Defaults: 30px height, 0.7rem text, primary fills, glow on hover.
================================================================= */
.v5-btn {
display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
height: 30px; padding: 0 var(--v5-sp-3);
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
height: 34px; padding: 0 var(--v5-sp-4);
font-family: var(--v5-font-sans);
font-size: .7rem; font-weight: var(--v5-fw-semi);
font-size: .8125rem; font-weight: var(--v5-fw-semi);
border: 1px solid transparent; border-radius: var(--v5-radius-md);
background: transparent; color: var(--v5-text);
cursor: pointer; user-select: none; white-space: nowrap;
@@ -25,7 +25,7 @@
transform .12s var(--v5-ease-move),
opacity .2s var(--v5-ease-move);
}
.v5-btn svg { width: 13px; height: 13px; stroke-width: 1.75; flex-shrink: 0; }
.v5-btn svg { width: 14px; height: 14px; stroke-width: 1.75; flex-shrink: 0; }
.v5-btn:disabled { opacity: .4; cursor: not-allowed; }
.v5-btn:active:not(:disabled) { transform: scale(.98); }
@@ -63,13 +63,20 @@
opacity: .92; box-shadow: var(--v5-glow-danger); transform: translateY(-1px);
}
/* size: sm — compact 26px */
/* size: sm — compact 28px */
.v5-btn.sm {
height: 26px; padding: 0 var(--v5-sp-2);
font-size: .64rem; border-radius: var(--v5-radius-sm);
height: 28px; padding: 0 var(--v5-sp-3);
font-size: .75rem; border-radius: var(--v5-radius-sm);
}
.v5-btn.sm svg { width: 12px; height: 12px; }
/* size: lg — prominent 40px */
.v5-btn.lg {
height: 40px; padding: 0 var(--v5-sp-5);
font-size: .875rem; border-radius: var(--v5-radius-md-2);
}
.v5-btn.lg svg { width: 16px; height: 16px; }
/* focus ring (keyboard) */
.v5-btn:focus-visible {
outline: none;
@@ -82,13 +89,13 @@
Pill, tiny caps, status-tinted.
================================================================= */
.v5-bdg {
display: inline-flex; align-items: center; gap: .3rem;
padding: .18rem .5rem;
font-size: .58rem; font-weight: var(--v5-fw-bold);
letter-spacing: var(--v5-ls-wide); text-transform: uppercase;
display: inline-flex; align-items: center; gap: .35rem;
padding: .22rem .55rem;
font-size: .72rem; font-weight: var(--v5-fw-semi);
border-radius: var(--v5-radius-pill);
background: var(--v5-bg-subtle); color: var(--v5-text-sec);
white-space: nowrap;
line-height: 1.2;
}
.v5-bdg .v5-bdg-dot {
width: 5px; height: 5px; border-radius: 50%;
@@ -122,9 +129,9 @@
gap: .6rem; margin-bottom: .4rem;
}
.v5-card-title {
font-size: .66rem; font-weight: var(--v5-fw-bold);
text-transform: uppercase; letter-spacing: var(--v5-ls-wide);
color: var(--v5-text-muted);
font-size: var(--v5-fs-body-sm); font-weight: 700;
color: var(--v5-text);
line-height: 1.3;
}
/* =================================================================
@@ -138,11 +145,9 @@
.v5-page-head-l { min-width: 0; flex: 1; }
.v5-page-head-r { display: flex; align-items: center; gap: .4rem; flex-shrink: 0; }
.v5-crumbs {
font-size: .6rem; color: var(--v5-text-muted);
display: flex; align-items: center; gap: .35rem;
font-family: var(--v5-font-mono);
letter-spacing: var(--v5-ls-wide);
margin-bottom: .25rem;
font-size: .72rem; color: var(--v5-text-muted);
display: flex; align-items: center; gap: .4rem;
margin-bottom: .3rem;
}
.v5-crumbs .sep { opacity: .5; }
.v5-page-title {
@@ -152,8 +157,8 @@
color: var(--v5-text);
}
.v5-page-sub {
font-size: .68rem; color: var(--v5-text-muted);
margin-top: .15rem;
font-size: var(--v5-fs-body-sm); color: var(--v5-text-muted);
margin-top: .25rem;
}
/* =================================================================
@@ -172,18 +177,16 @@
}
.v5-tbl thead th {
text-align: left;
padding: var(--v5-sp-3) var(--v5-sp-4);
font-size: var(--v5-fs-caption);
font-weight: var(--v5-fw-bold);
letter-spacing: var(--v5-ls-wide);
text-transform: uppercase;
color: var(--v5-text-muted);
padding: .7rem var(--v5-sp-4);
font-size: var(--v5-fs-caption-lg);
font-weight: 600;
color: var(--v5-text-sec);
background: var(--v5-bg-subtle);
border-bottom: 1px solid var(--v5-border);
white-space: nowrap;
}
.v5-tbl tbody td {
padding: .55rem var(--v5-sp-4);
padding: .65rem var(--v5-sp-4);
border-bottom: 1px solid var(--v5-border-subtle);
vertical-align: middle;
}
@@ -205,12 +208,12 @@
Large number with trend pill underneath.
================================================================= */
.v5-kpi-num {
font-size: 1.85rem; font-weight: 800;
font-size: var(--v5-fs-display); font-weight: 800;
letter-spacing: -0.03em;
font-variant-numeric: tabular-nums;
line-height: 1.05;
color: var(--v5-text);
display: flex; align-items: baseline; gap: .55rem;
display: flex; align-items: baseline; gap: .6rem;
}
.v5-kpi-num.kpi-cyan { color: rgb(var(--v5-cyan-rgb)); }
.v5-kpi-num.kpi-green { color: rgb(var(--v5-green-rgb)); }
@@ -218,18 +221,18 @@
.v5-kpi-num.kpi-amber { color: rgb(var(--v5-amber-rgb)); }
.v5-kpi-delta {
display: inline-flex; align-items: center; gap: .15rem;
font-size: .62rem; font-weight: var(--v5-fw-bold);
padding: .12rem .4rem; border-radius: var(--v5-radius-sm);
display: inline-flex; align-items: center; gap: .2rem;
font-size: .75rem; font-weight: var(--v5-fw-bold);
padding: .18rem .5rem; border-radius: var(--v5-radius-sm);
}
.v5-kpi-delta.up { background: rgba(var(--v5-green-rgb), .12); color: rgb(var(--v5-green-rgb)); }
.v5-kpi-delta.down { background: rgba(var(--v5-red-rgb), .12); color: rgb(var(--v5-red-rgb)); }
.v5-kpi-delta svg { width: 11px; height: 11px; stroke-width: 2; }
.v5-kpi-sub {
font-size: .62rem;
font-size: .75rem;
color: var(--v5-text-muted);
margin-top: .25rem;
margin-top: .3rem;
}
/* =================================================================
@@ -287,9 +290,9 @@
.v5-feed-txt b { color: var(--v5-text); font-weight: var(--v5-fw-semi); }
.v5-feed-txt .tm {
display: block;
font-size: .6rem; color: var(--v5-text-muted);
font-size: .72rem; color: var(--v5-text-muted);
font-family: var(--v5-font-mono);
margin-top: .15rem;
margin-top: .2rem;
}
/* =================================================================
@@ -390,7 +393,7 @@
animation: v5-overlay-in .22s var(--v5-ease-enter);
}
.v5-modal {
width: 100%; max-width: 420px;
width: 100%; max-width: 560px;
background: var(--v5-surface-solid);
border: 1px solid var(--v5-border);
border-radius: var(--v5-radius-lg-2);
@@ -398,6 +401,9 @@
padding: var(--v5-sp-5);
animation: v5-modal-in .3s var(--v5-ease-enter);
}
.v5-modal.sm { max-width: 420px; }
.v5-modal.lg { max-width: 720px; }
.v5-modal.xl { max-width: 960px; }
.v5-modal-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: .6rem; margin-bottom: .7rem;
@@ -409,10 +415,10 @@
color: var(--v5-text);
}
.v5-modal-body {
font-size: var(--v5-fs-body-sm);
font-size: var(--v5-fs-body);
color: var(--v5-text-sec);
line-height: 1.55;
margin-bottom: 1rem;
margin-bottom: 1.1rem;
}
.v5-modal-body b { color: var(--v5-text); font-weight: var(--v5-fw-bold); }
.v5-modal-foot {
+15 -12
View File
@@ -64,15 +64,15 @@
--v5-font-mono: 'JetBrains Mono', 'D2Coding', ui-monospace, SFMono-Regular, Monaco, Consolas, monospace;
/* ===== Type scale (deliberately dense, ERP). Do not go above these. ===== */
--v5-fs-caption:0.60rem; /* labels, table headers, chips */
--v5-fs-caption-lg:0.68rem;
--v5-fs-body-sm:0.72rem; /* table rows */
--v5-fs-body:0.78rem; /* default body */
--v5-fs-body-lg:0.85rem; /* max body */
--v5-fs-h3:0.92rem;
--v5-fs-h2:1.00rem;
--v5-fs-h1:1.12rem; /* page/section title */
--v5-fs-display:1.60rem; /* KPI numbers only */
--v5-fs-caption:0.75rem; /* labels, table headers, chips */
--v5-fs-caption-lg:0.8125rem;
--v5-fs-body-sm:0.8125rem; /* table rows */
--v5-fs-body:0.875rem; /* default body */
--v5-fs-body-lg:0.9375rem; /* max body */
--v5-fs-h3:1.0625rem;
--v5-fs-h2:1.1875rem;
--v5-fs-h1:1.375rem; /* page/section title */
--v5-fs-display:2rem; /* KPI numbers only */
--v5-fw-regular:400;
--v5-fw-semi:600;
@@ -220,7 +220,7 @@ html:not(.dark) .v5-hdr{
.v5-hdr-logo{font-size:1.05rem;font-weight:900;letter-spacing:-.03em;
background:linear-gradient(135deg,var(--v5-primary),var(--v5-cyan));-webkit-background-clip:text;
-webkit-text-fill-color:transparent;background-clip:text;cursor:default;}
.v5-hdr-bc{font-size:.72rem;color:var(--v5-text-muted);}
.v5-hdr-bc{font-size:.8125rem;color:var(--v5-text-muted);}
.v5-hdr-bc b{color:var(--v5-text);font-weight:600;}
.v5-hdr-r{display:flex;align-items:center;gap:.65rem;}
@@ -479,8 +479,8 @@ html:not(.dark) .v5-side{
transition:opacity .3s,height .3s,padding .3s;}
.v5-side-sec:first-child{padding-top:.25rem;}
.v5-si{padding:.5rem .7rem;border-radius:10px;font-size:.77rem;color:var(--v5-text-sec);cursor:pointer;
transition:all .25s cubic-bezier(.4,0,.2,1);font-weight:450;display:flex;align-items:center;gap:.6rem;
.v5-si{padding:.55rem .75rem;border-radius:10px;font-size:.8125rem;color:var(--v5-text-sec);cursor:pointer;
transition:all .25s cubic-bezier(.4,0,.2,1);font-weight:500;display:flex;align-items:center;gap:.6rem;
position:relative;overflow:hidden;height:auto;}
.v5-si .ic{width:16px;height:16px;display:flex;align-items:center;justify-content:center;opacity:.65;flex-shrink:0;}
.v5-si:hover{background:var(--v5-surface-hover);color:var(--v5-text);transform:translateX(2px);}
@@ -1446,3 +1446,6 @@ html.vt-color-changing .settings-color-swatch.on,
html.vt-color-changing .v5-bell,
html.vt-color-changing .v5-admin-btn{
animation:v5-color-refresh .55s cubic-bezier(.34,1.4,.64,1) both;}
/* ===== Header separator (v5 redesign 2026-04-22) ===== */
.v5-hdr-sep{display:inline-block;width:1px;height:18px;background:var(--v5-border);margin:0 .35rem;flex-shrink:0;opacity:.7;}