mirror of
https://github.com/hansjone/netx.git
synced 2026-10-16 11:30:50 +08:00
Redo the login page: static milky-way sky and a droplet-glass card.
The space photo is replaced by a seeded canvas sky (nebula band, dust lanes, stars) painted once. The card refracts the sky at its rim through an SVG displacement filter (Chromium; plain blur elsewhere), sits further from the right edge, and drops the glowing flare and button shadow. Login errors now read as text instead of API codes, the last username is remembered, and Caps Lock is flagged in the password field. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e21719b66c
commit
ac162dff4e
9 changed files with 497 additions and 97 deletions
|
|
@ -30,7 +30,17 @@
|
|||
/* Center earth within the left stage (not under the form rail). */
|
||||
justify-content: center;
|
||||
padding: 3vh calc(var(--login-rail-w) + 3vw) 3vh 5vw;
|
||||
background: url("/login-space.webp") center / cover no-repeat;
|
||||
background: #01040b;
|
||||
}
|
||||
|
||||
/* Procedural sky (LoginStarfield), painted once. */
|
||||
.login-sky {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-page__space::before {
|
||||
|
|
@ -51,39 +61,6 @@
|
|||
linear-gradient(180deg, rgba(0, 0, 0, 0.1) 0%, transparent 30%, transparent 72%, rgba(0, 0, 0, 0.38) 100%);
|
||||
}
|
||||
|
||||
/* Tiny sun flare — upper-left void. */
|
||||
.login-page__flares {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-page__flare {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.login-page__flare--a {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
left: 7%;
|
||||
top: 11%;
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 50% 50%,
|
||||
rgba(245, 250, 255, 0.92) 0%,
|
||||
rgba(170, 210, 255, 0.48) 28%,
|
||||
rgba(90, 150, 245, 0.14) 55%,
|
||||
transparent 78%
|
||||
);
|
||||
box-shadow: 0 0 16px 3px rgba(120, 180, 255, 0.2);
|
||||
animation: login-flare-pulse-a 10s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.login-planet {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
|
@ -458,14 +435,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes login-flare-pulse-a {
|
||||
from { opacity: 0.7; transform: scale(0.96); }
|
||||
to { opacity: 1; transform: scale(1.08); }
|
||||
}
|
||||
|
||||
/* Pause infinite loops when the login tab is backgrounded (class set by LoginShell). */
|
||||
html.login-page--paused .login-planet__textures,
|
||||
html.login-page--paused .login-page__flare {
|
||||
html.login-page--paused .login-planet__textures {
|
||||
animation-play-state: paused !important;
|
||||
}
|
||||
|
||||
|
|
@ -475,8 +446,7 @@ html.login-page--paused .login-page__flare {
|
|||
.login-planet__earth::after,
|
||||
.login-card,
|
||||
.login-card__brand,
|
||||
.login-card__error,
|
||||
.login-page__flare {
|
||||
.login-card__error {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
|
|
@ -490,10 +460,6 @@ html.login-page--paused .login-page__flare {
|
|||
animation-iteration-count: infinite !important;
|
||||
}
|
||||
|
||||
.login-page__flare {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.login-planet {
|
||||
transform: rotate(5deg);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -446,44 +446,73 @@ a,
|
|||
.login-page {
|
||||
--login-accent: var(--nx-accent-strong);
|
||||
--login-muted: var(--nx-text-muted);
|
||||
--login-field: rgb(1 5 12 / 0.32);
|
||||
--login-field-border: rgb(150 190 255 / 0.08);
|
||||
--login-rail-w: clamp(440px, 36vw, 560px);
|
||||
}
|
||||
|
||||
/* Pull the card in from the right edge; the earth re-centers on the
|
||||
remaining stage because .login-page__space pads by --login-rail-w. */
|
||||
.login-page__rail {
|
||||
padding: 48px clamp(64px, 8vw, 140px) 48px 0;
|
||||
}
|
||||
|
||||
/* Droplet glass: almost clear, the rim refracts the stars behind it
|
||||
(DropletGlassFilter, Chromium) with a crisp specular edge and a soft
|
||||
contact shadow. Non-Chromium engines keep the plain blur line. */
|
||||
.login-card {
|
||||
max-width: 372px;
|
||||
gap: 18px;
|
||||
padding: 32px 30px 30px;
|
||||
border-radius: 20px;
|
||||
border-color: rgb(255 255 255 / 0.12);
|
||||
background: rgb(var(--nx-ink-rgb) / 0.78);
|
||||
backdrop-filter: blur(14px) saturate(1.15);
|
||||
-webkit-backdrop-filter: blur(14px) saturate(1.15);
|
||||
position: relative;
|
||||
max-width: 360px;
|
||||
gap: 22px;
|
||||
padding: 38px 32px 32px;
|
||||
border-radius: 30px;
|
||||
border: 0;
|
||||
background:
|
||||
radial-gradient(120% 70% at 30% 0%, rgb(190 220 255 / 0.06) 0%, transparent 60%),
|
||||
rgb(3 9 20 / 0.16);
|
||||
-webkit-backdrop-filter: blur(3px) saturate(150%) brightness(0.9);
|
||||
backdrop-filter: blur(3px) saturate(150%) brightness(0.9);
|
||||
backdrop-filter: url(#login-droplet) saturate(150%) brightness(0.9);
|
||||
box-shadow:
|
||||
inset 0 1px 1px rgb(220 235 255 / 0.22),
|
||||
inset 0 -1px 1px rgb(220 235 255 / 0.06),
|
||||
inset 14px 18px 28px -22px rgb(190 220 255 / 0.22),
|
||||
inset -14px -18px 28px -22px rgb(120 170 230 / 0.14),
|
||||
0 24px 48px -12px rgb(0 0 0 / 0.55),
|
||||
0 4px 10px rgb(0 0 0 / 0.25);
|
||||
}
|
||||
|
||||
/* Specular ring: brightest where light enters (top-left) and exits (bottom-right). */
|
||||
.login-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 1px;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(135deg,
|
||||
rgb(210 230 255 / 0.42) 0%, rgb(190 215 255 / 0.12) 22%, rgb(170 205 255 / 0.03) 45%,
|
||||
rgb(170 205 255 / 0.03) 60%, rgb(170 205 255 / 0.12) 82%, rgb(200 225 255 / 0.30) 100%);
|
||||
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-card__head {
|
||||
flex-direction: column-reverse;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
margin-bottom: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.login-card__brand {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.28em;
|
||||
background: linear-gradient(90deg, #ffffff 0%, #8fc2ff 100%);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.3em;
|
||||
background: linear-gradient(90deg, var(--nx-text-strong) 0%, var(--nx-accent-text) 100%);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.login-card__title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--nx-text-muted);
|
||||
}
|
||||
|
||||
/* The username TextField sits outside .login-card__label/__password, so it
|
||||
rendered with HeroUI defaults and did not match the password field. */
|
||||
.login-card__form input {
|
||||
|
|
@ -491,32 +520,76 @@ a,
|
|||
height: 46px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--login-field-border);
|
||||
border-radius: 12px;
|
||||
border-radius: 16px;
|
||||
background: var(--login-field);
|
||||
color: #fff;
|
||||
box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.35), 0 1px 0 rgb(200 225 255 / 0.05);
|
||||
color: var(--nx-text-strong);
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
box-sizing: border-box;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
|
||||
transition: border-color 160ms ease, background 160ms ease;
|
||||
}
|
||||
|
||||
.login-card__form input::placeholder {
|
||||
color: rgb(255 255 255 / 0.45);
|
||||
color: var(--nx-text-faint);
|
||||
}
|
||||
|
||||
.login-card__form input:focus {
|
||||
outline: none;
|
||||
border-color: rgb(var(--nx-accent-rgb) / 0.8);
|
||||
box-shadow: 0 0 0 3px rgb(var(--nx-accent-rgb) / 0.25);
|
||||
background: rgb(255 255 255 / 0.1);
|
||||
border-color: rgb(var(--nx-accent-rgb) / 0.5);
|
||||
box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.35), 0 1px 0 rgb(200 225 255 / 0.05);
|
||||
background: rgb(1 5 12 / 0.42);
|
||||
}
|
||||
|
||||
.login-card__form {
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.login-card__submit {
|
||||
height: 46px;
|
||||
border-radius: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
.login-card__eye {
|
||||
color: var(--nx-text-muted);
|
||||
}
|
||||
|
||||
.login-card__caps {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 44px;
|
||||
z-index: 2;
|
||||
transform: translateY(-50%);
|
||||
padding: 2px 7px;
|
||||
border-radius: 6px;
|
||||
background: rgb(var(--nx-warn-rgb) / 0.12);
|
||||
color: var(--nx-warn);
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.login-card__error {
|
||||
color: var(--nx-bad-text);
|
||||
background: rgb(var(--nx-bad-rgb) / 0.1);
|
||||
border-color: rgb(var(--nx-bad-rgb) / 0.22);
|
||||
}
|
||||
|
||||
.login-card__submit {
|
||||
margin-top: 6px;
|
||||
height: 46px;
|
||||
border-radius: 16px;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(255 255 255 / 0.14) 0%, rgb(255 255 255 / 0) 55%),
|
||||
var(--nx-gradient-accent);
|
||||
color: var(--nx-text-strong);
|
||||
letter-spacing: 0.08em;
|
||||
box-shadow:
|
||||
inset 0 1px 1px rgb(255 255 255 / 0.25),
|
||||
inset 0 -1px 2px rgb(0 0 0 / 0.2),
|
||||
0 6px 14px -6px rgb(0 0 0 / 0.6);
|
||||
filter: brightness(0.86);
|
||||
}
|
||||
|
||||
.login-card__submit:hover:not(:disabled) {
|
||||
filter: brightness(0.94);
|
||||
}
|
||||
|
||||
.login-card__submit:active:not(:disabled) {
|
||||
filter: brightness(0.82);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue