diff --git a/web/index.html b/web/index.html index ea18e86..c826e7c 100644 --- a/web/index.html +++ b/web/index.html @@ -4,6 +4,12 @@ + + + NETX diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 3293173..a9f627f 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -440,7 +440,7 @@ const en = { langEn: "English", }, auth: { - loginTitle: "Sign in to NETX", + loginTitle: "Sign in", username: "Username", password: "Password", login: "Sign in", diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index bb9f572..05e3845 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -437,7 +437,7 @@ const zh = { langEn: "English", }, auth: { - loginTitle: "登录 NETX", + loginTitle: "登录", username: "用户名", password: "密码", login: "登录", diff --git a/web/src/index.css b/web/src/index.css index d0559e1..351593c 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -6467,81 +6467,276 @@ pre { } .login-page { + --login-ink: #0f2744; + --login-accent: #0e7490; + --login-accent-deep: #155e75; + --login-panel: rgba(255, 255, 255, 0.88); + --login-line: rgba(15, 39, 68, 0.12); + --login-muted: #5b6b7c; + --login-text: #243447; + --login-danger: #b42318; + position: relative; + isolation: isolate; min-height: 100vh; display: flex; align-items: center; justify-content: center; - padding: 24px; + padding: 28px 20px; + overflow: hidden; background: - radial-gradient(ellipse at 20% 20%, rgba(33, 150, 243, 0.18), transparent 55%), - radial-gradient(ellipse at 80% 0%, rgba(21, 101, 192, 0.2), transparent 45%), - #eef2f7; + linear-gradient(160deg, #e8eef5 0%, #f3f7fa 42%, #e4eef2 100%); + font-family: "IBM Plex Sans", "Segoe UI", sans-serif; + color: var(--login-text); +} + +.login-page__atmosphere { + position: absolute; + inset: 0; + z-index: 0; + pointer-events: none; + overflow: hidden; +} + +.login-page__orb { + position: absolute; + border-radius: 50%; + filter: blur(8px); + opacity: 0.55; + animation: login-orb-drift 18s ease-in-out infinite alternate; +} + +.login-page__orb--a { + width: min(52vw, 520px); + height: min(52vw, 520px); + top: -12%; + left: -8%; + background: radial-gradient(circle at 35% 35%, #7dd3fc 0%, rgba(14, 116, 144, 0.25) 42%, transparent 70%); +} + +.login-page__orb--b { + width: min(48vw, 460px); + height: min(48vw, 460px); + right: -10%; + bottom: -18%; + background: radial-gradient(circle at 40% 40%, #93c5fd 0%, rgba(15, 39, 68, 0.18) 48%, transparent 72%); + animation-delay: -6s; + animation-duration: 22s; +} + +.login-page__grid { + position: absolute; + inset: -20%; + opacity: 0.28; + background-image: + linear-gradient(rgba(15, 39, 68, 0.06) 1px, transparent 1px), + linear-gradient(90deg, rgba(15, 39, 68, 0.06) 1px, transparent 1px); + background-size: 48px 48px; + mask-image: radial-gradient(ellipse at 50% 45%, #000 20%, transparent 72%); + animation: login-grid-pan 28s linear infinite; } .login-card { + position: relative; + z-index: 1; width: min(400px, 100%); - padding: 28px 28px 24px; - background: #fff; - border: 1px solid #d8dee8; - border-radius: 10px; - box-shadow: 0 10px 30px rgba(15, 23, 42, 0.08); + padding: 36px 32px 28px; + background: var(--login-panel); + border: 1px solid var(--login-line); + border-radius: 16px; + backdrop-filter: blur(14px); + -webkit-backdrop-filter: blur(14px); display: flex; flex-direction: column; - gap: 12px; + gap: 14px; + animation: login-card-in 520ms cubic-bezier(0.22, 1, 0.36, 1) both; } .login-card__brand { - font-size: 22px; + font-family: Sora, "IBM Plex Sans", sans-serif; + font-size: clamp(2.4rem, 6vw, 3rem); font-weight: 700; - color: #1565c0; - letter-spacing: 0.02em; + line-height: 1; + letter-spacing: 0.08em; + color: var(--login-ink); + animation: login-brand-in 700ms cubic-bezier(0.22, 1, 0.36, 1) both; } .login-card__title { - margin: 0; - font-size: 20px; - color: #0f172a; + margin: 0 0 4px; + font-family: Sora, "IBM Plex Sans", sans-serif; + font-size: 1.05rem; + font-weight: 600; + letter-spacing: 0.01em; + color: var(--login-muted); } .login-card__hint { - margin: 0; - color: #64748b; + margin: -4px 0 2px; + color: var(--login-muted); font-size: 13px; + line-height: 1.45; } .login-card__label { display: flex; flex-direction: column; - gap: 6px; + gap: 7px; font-size: 13px; - color: #334155; + font-weight: 500; + color: var(--login-ink); } .login-card__label input { - height: 36px; - padding: 0 10px; + height: 44px; + padding: 0 14px; + border: 1px solid var(--login-line); + border-radius: 10px; + background: rgba(255, 255, 255, 0.92); + color: var(--login-ink); + font: inherit; + font-size: 15px; + transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease; +} + +.login-card__label input:hover:not(:disabled) { + border-color: rgba(14, 116, 144, 0.35); +} + +.login-card__label input:focus { + outline: none; + border-color: var(--login-accent); + box-shadow: 0 0 0 3px rgba(14, 116, 144, 0.18); + background: #fff; +} + +.login-card__label input:disabled { + opacity: 0.65; + cursor: not-allowed; } .login-card__error { - color: #b91c1c; + color: var(--login-danger); font-size: 13px; + line-height: 1.4; + padding: 8px 10px; + border-radius: 8px; + background: rgba(180, 35, 24, 0.06); + border: 1px solid rgba(180, 35, 24, 0.14); + animation: login-error-in 220ms ease both; } .login-card__submit { - margin-top: 4px; - height: 38px; + margin-top: 6px; + height: 44px; border: 0; - border-radius: 6px; - background: #1565c0; + border-radius: 10px; + background: linear-gradient(180deg, #0e8aa8 0%, var(--login-accent-deep) 100%); color: #fff; + font-family: Sora, "IBM Plex Sans", sans-serif; + font-size: 15px; font-weight: 600; + letter-spacing: 0.02em; + cursor: pointer; + transition: transform 140ms ease, filter 140ms ease, opacity 140ms ease; +} + +.login-card__submit:hover:not(:disabled) { + filter: brightness(1.05); + transform: translateY(-1px); +} + +.login-card__submit:active:not(:disabled) { + transform: translateY(0); + filter: brightness(0.98); } .login-card__submit:disabled { - opacity: 0.6; + opacity: 0.55; cursor: not-allowed; } +.login-card__submit + .login-card__submit { + margin-top: 0; + background: #64748b; +} + +@keyframes login-card-in { + from { + opacity: 0; + transform: translateY(18px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes login-brand-in { + from { + opacity: 0; + letter-spacing: 0.22em; + transform: translateY(8px); + } + to { + opacity: 1; + letter-spacing: 0.08em; + transform: translateY(0); + } +} + +@keyframes login-orb-drift { + from { + transform: translate3d(0, 0, 0) scale(1); + } + to { + transform: translate3d(3%, 4%, 0) scale(1.06); + } +} + +@keyframes login-grid-pan { + from { + transform: translate3d(0, 0, 0); + } + to { + transform: translate3d(-48px, -24px, 0); + } +} + +@keyframes login-error-in { + from { + opacity: 0; + transform: translateY(-4px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (prefers-reduced-motion: reduce) { + .login-page__orb, + .login-page__grid, + .login-card, + .login-card__brand, + .login-card__error { + animation: none !important; + } + + .login-card__submit:hover:not(:disabled) { + transform: none; + } +} + +@media (max-width: 480px) { + .login-card { + padding: 28px 20px 22px; + } + + .login-card__brand { + font-size: 2.2rem; + } +} + /* —— Network Management shell (left nav + main) —— Classic ops palette aligned with brand header #0f2744: cooler slate chrome, white content cards, blue active accent. */ diff --git a/web/src/pages/ForceChangePasswordPage.tsx b/web/src/pages/ForceChangePasswordPage.tsx index 2f4d6c1..3581123 100644 --- a/web/src/pages/ForceChangePasswordPage.tsx +++ b/web/src/pages/ForceChangePasswordPage.tsx @@ -43,8 +43,15 @@ export function ForceChangePasswordPage() { return (
+
void onSubmit(e)}> -
NETX
+
+ NETX +

{t("auth.forceChangeTitle")}

{t("auth.forceChangeHint", { user: user?.username || "admin" })} diff --git a/web/src/pages/LoginPage.tsx b/web/src/pages/LoginPage.tsx index 572c630..272a7df 100644 --- a/web/src/pages/LoginPage.tsx +++ b/web/src/pages/LoginPage.tsx @@ -32,8 +32,15 @@ export function LoginPage() { return (

+ void onSubmit(e)}> -
NETX
+
+ NETX +

{t("auth.loginTitle")}

- {error ?
{error}
: null} + {error ? ( +
+ {error} +
+ ) : null}