TradeMind / frontend /src /auth.css
KSS-1227's picture
sign up sign in
2728a40
Raw History Blame Contribute Delete
15.7 kB
/* ─────────────────────────────────────────────────────────────
auth.css β€” TradeMind authentication shell
───────────────────────────────────────────────────────────── */
/* ── Page shell ────────────────────────────────────────────── */
.auth-shell {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background:
radial-gradient(ellipse at 20% 10%, rgba(0, 201, 167, 0.07) 0%, transparent 55%),
radial-gradient(ellipse at 80% 90%, rgba(0, 130, 255, 0.07) 0%, transparent 55%),
radial-gradient(circle at top, #123352 0%, #080e1a 65%, #050816 100%);
padding: 24px;
animation: fadeIn 450ms ease-out;
}
/* ── Card ──────────────────────────────────────────────────── */
.auth-card {
width: min(520px, 92vw);
background: #0f1f36;
border: 1px solid rgba(30, 58, 95, 0.9);
border-radius: 24px;
padding: 40px;
box-shadow:
0 24px 64px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(0, 201, 167, 0.05) inset;
animation: slideUp 500ms cubic-bezier(0.22, 1, 0.36, 1);
display: flex;
flex-direction: column;
gap: 28px;
}
/* ── Header ────────────────────────────────────────────────── */
.auth-header {
display: flex;
flex-direction: column;
gap: 16px;
}
.brand-block {
display: flex;
align-items: center;
gap: 12px;
}
.brand-mark {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border-radius: 12px;
background: linear-gradient(135deg, #00c9a7, #0082ff);
color: #031019;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 16px rgba(0, 201, 167, 0.35);
}
.auth-header h1 {
margin: 0;
color: #f0f4f8;
font-size: 24px;
font-weight: 800;
letter-spacing: -0.3px;
}
.auth-header p {
margin: 3px 0 0;
color: #64748b;
font-size: 13px;
}
/* ── Mode switch (tabs) ─────────────────────────────────────── */
.auth-tabs {
display: flex;
gap: 4px;
padding: 4px;
background: #132744;
border-radius: 999px;
border: 1px solid rgba(30, 58, 95, 0.8);
}
.tab {
flex: 1;
border: none;
border-radius: 999px;
padding: 10px 14px;
background: transparent;
color: #475569;
font-weight: 700;
font-size: 13px;
cursor: pointer;
transition: all 250ms cubic-bezier(0.22, 1, 0.36, 1);
}
.tab:hover:not(.active) {
color: #94a3b8;
background: rgba(255, 255, 255, 0.04);
}
.tab.active {
background: linear-gradient(135deg, #14d8c4, #1aa6e8);
color: #fff;
box-shadow: 0 8px 20px rgba(20, 216, 196, 0.35);
}
/* ── Form ──────────────────────────────────────────────────── */
.auth-form {
display: flex;
flex-direction: column;
gap: 18px;
}
/* ── Field group ───────────────────────────────────────────── */
.field-group {
display: flex;
flex-direction: column;
gap: 0;
}
/* ── Input wrapper ─────────────────────────────────────────── */
.input-wrapper {
position: relative;
display: flex;
align-items: center;
min-height: 56px;
border-radius: 12px;
border: 1.5px solid rgba(30, 58, 95, 0.9);
background: #132840;
transition:
border-color 200ms ease,
box-shadow 200ms ease,
transform 200ms ease;
}
.input-wrapper--focused {
border-color: #00c9a7;
box-shadow: 0 0 0 3px rgba(0, 201, 167, 0.15);
transform: translateY(-1px);
}
.input-wrapper--error {
border-color: #f25c54;
box-shadow: 0 0 0 3px rgba(242, 92, 84, 0.12);
}
.input-wrapper--error.input-wrapper--focused {
border-color: #f25c54;
box-shadow: 0 0 0 3px rgba(242, 92, 84, 0.18);
}
/* ── Left icon ─────────────────────────────────────────────── */
.input-icon {
position: absolute;
left: 14px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
font-size: 18px;
color: #6b7280;
flex-shrink: 0;
pointer-events: none;
transition: color 200ms ease;
z-index: 2;
}
.input-icon--active {
color: #00c9a7;
}
/* ── Floating label ────────────────────────────────────────── */
/*
Resting state β€” sits vertically centred, looks like a placeholder.
Active state β€” floats to the TOP of the wrapper (above typed text).
This matches the Stripe / Vercel / Linear pattern.
*/
.floating-label {
position: absolute;
left: 13px;
top: 50%;
transform: translateY(-50%);
font-size: 15px;
font-weight: 500;
color: #475569;
pointer-events: none;
transition:
top 200ms cubic-bezier(0.22, 1, 0.36, 1),
transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
font-size 200ms cubic-bezier(0.22, 1, 0.36, 1),
color 200ms ease;
white-space: nowrap;
z-index: 1;
}
/* Shift right when icon is present */
.floating-label--with-icon {
left: 40px;
}
/* Floated-up state (focused or has value) */
.floating-label--active {
top: 8px;
transform: translateY(0);
font-size: 11px;
font-weight: 700;
color: #00c9a7;
letter-spacing: 0.08em;
text-transform: uppercase;
}
/* Filled but not focused β€” stay floated, turn muted (Stripe/Vercel pattern) */
.floating-label--filled {
color: #64748b;
}
/* ── Input itself ──────────────────────────────────────────── */
.auth-input {
width: 100%;
height: 56px;
padding: 22px 16px 8px 16px;
background: transparent;
color: #f0f4f8;
font-size: 15px;
line-height: 22px;
outline: none;
border: none;
border-radius: 12px;
caret-color: #00c9a7;
min-width: 0;
box-sizing: border-box;
}
/* Shift text right when icon present β€” icon is 14px left + 18px wide + 8px gap */
.auth-input--with-icon {
padding-left: 40px;
}
/* Right padding for password eye toggle */
.auth-input--with-toggle {
padding-right: 60px;
}
/* Hide the browser's built-in password reveal button so it doesn't
double up with our custom toggle β€” Edge, IE, and Chromium-based. */
.auth-input::-ms-reveal,
.auth-input::-ms-clear {
display: none;
}
input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear {
display: none;
}
.auth-input::-webkit-credentials-auto-fill-button,
.auth-input::-webkit-contacts-auto-fill-button {
display: none !important;
}
.auth-input::placeholder {
color: #334155;
font-size: 14px;
}
/* Autofill override β€” handled by keyframes above */
/* ── Password show/hide toggle ─────────────────────────────── */
.password-toggle {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: none;
border: none;
color: #475569;
cursor: pointer;
border-radius: 6px;
transition: color 180ms ease, background 180ms ease;
z-index: 5;
}
.password-toggle:hover {
color: #00c9a7;
background: rgba(0, 201, 167, 0.1);
}
.password-toggle:focus-visible {
outline: 2px solid #00c9a7;
outline-offset: 2px;
}
/* ── Field hint & error ────────────────────────────────────── */
.field-hint {
margin-top: 8px;
margin-left: 4px;
font-size: 12px;
color: #64748b;
line-height: 1.4;
}
.field-error {
margin-top: 6px;
margin-left: 4px;
font-size: 12px;
color: #f25c54;
animation: shakeIn 250ms ease;
line-height: 1.4;
}
/* ── Auth options row (Remember me + Forgot) ───────────────── */
.auth-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 4px;
margin-bottom: 4px;
gap: 8px;
flex-wrap: wrap;
}
.checkbox-row {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #64748b;
cursor: pointer;
user-select: none;
}
.checkbox-row input[type="checkbox"] {
accent-color: #00c9a7;
width: 15px;
height: 15px;
cursor: pointer;
}
.checkbox-row-large {
margin-top: 2px;
}
/* ── Forgot / terms link ───────────────────────────────────── */
.link-button {
border: none;
background: transparent;
color: #00c9a7;
font-weight: 700;
cursor: pointer;
padding: 0;
font-size: 13px;
transition: opacity 180ms ease;
}
.link-button:hover {
opacity: 0.75;
}
/* ── Submit button ─────────────────────────────────────────── */
.auth-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
width: 100%;
height: 54px;
margin-top: 12px;
border: none;
border-radius: 14px;
background: linear-gradient(135deg, #00c9a7 0%, #00a8d8 100%);
color: #031019;
font-weight: 800;
font-size: 17px;
cursor: pointer;
letter-spacing: 0.2px;
transition:
transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 200ms ease,
opacity 200ms ease;
overflow: hidden;
}
.auth-button::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(135deg, #00ddb8, #00bce8);
opacity: 0;
transition: opacity 200ms ease;
}
.auth-button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 12px 28px rgba(0, 201, 167, 0.3);
}
.auth-button:hover:not(:disabled)::before {
opacity: 1;
}
.auth-button:active:not(:disabled) {
transform: translateY(0);
box-shadow: 0 4px 12px rgba(0, 201, 167, 0.2);
}
.auth-button > * {
position: relative;
}
.auth-button.is-loading {
opacity: 0.88;
cursor: progress;
}
.auth-button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
/* ── Spinner ───────────────────────────────────────────────── */
.check-mark {
font-size: 16px;
font-weight: 900;
}
.spinner {
width: 15px;
height: 15px;
border-radius: 50%;
border: 2px solid rgba(3, 16, 25, 0.3);
border-top-color: #031019;
animation: spin 700ms linear infinite;
}
/* ── Password strength ─────────────────────────────────────── */
.password-strength {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 8px;
margin-bottom: 16px;
}
.password-strength-header {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 11px;
color: #94a3b8;
}
.strength-badge {
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
transition: color 220ms ease;
}
.strength-badge.weak { color: #f25c54; }
.strength-badge.medium { color: #f6c90e; }
.strength-badge.strong { color: #00c9a7; }
.strength-bar {
width: 100%;
height: 5px;
background: rgba(148, 163, 184, 0.15);
border-radius: 999px;
overflow: hidden;
}
.strength-fill {
height: 100%;
border-radius: inherit;
transition: width 260ms cubic-bezier(0.22, 1, 0.36, 1);
}
.strength-fill.weak { background: #f25c54; }
.strength-fill.medium { background: #f6c90e; }
.strength-fill.strong { background: #00c9a7; }
/* ── Auth message banner (fallback) ────────────────────────── */
.auth-message {
font-size: 13px;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid transparent;
}
.auth-message.error {
color: #f0f4f8;
background: rgba(242, 92, 84, 0.14);
border-color: rgba(242, 92, 84, 0.3);
}
.auth-message.info {
color: #f0f4f8;
background: rgba(0, 201, 167, 0.12);
border-color: rgba(0, 201, 167, 0.3);
}
/* ── Forgot Password screen ────────────────────────────────── */
.forgot-screen {
display: flex;
flex-direction: column;
gap: 20px;
animation: fadeIn 300ms ease-out;
}
.forgot-back {
align-self: flex-start;
background: none;
border: none;
color: #64748b;
font-size: 13px;
font-weight: 600;
cursor: pointer;
padding: 0;
transition: color 180ms ease;
}
.forgot-back:hover {
color: #00c9a7;
}
.forgot-header {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.forgot-icon {
font-size: 32px;
line-height: 1;
margin-bottom: 4px;
}
.forgot-title {
margin: 0;
font-size: 18px;
font-weight: 800;
color: #f0f4f8;
}
.forgot-sub {
margin: 0;
font-size: 13px;
color: #64748b;
line-height: 1.55;
max-width: 300px;
}
.forgot-success {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
text-align: center;
padding: 8px 0;
}
.forgot-success-icon {
font-size: 36px;
}
.forgot-success p {
font-size: 13px;
color: #94a3b8;
line-height: 1.6;
}
.forgot-success strong {
color: #f0f4f8;
}
/* ── Keyframes ─────────────────────────────────────────────── */
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@keyframes shakeIn {
0% { transform: translateX(0); }
25% { transform: translateX(-4px); }
50% { transform: translateX(4px); }
75% { transform: translateX(-2px); }
100% { transform: translateX(0); }
}
/*
Autofill detection keyframes.
Chrome fires a CSS animation on :-webkit-autofill β€” we name them so
React's onAnimationStart can detect the event and float the label
before the controlled value state catches up.
*/
@keyframes autofillStart {
from { /* intentionally empty */ }
to { /* intentionally empty */ }
}
@keyframes autofillEnd {
from { /* intentionally empty */ }
to { /* intentionally empty */ }
}
.auth-input:-webkit-autofill {
animation-name: autofillStart;
animation-duration: 1ms;
/* Keep dark background β€” overrides Chrome's yellow autofill tint */
-webkit-box-shadow: 0 0 0 1000px #132840 inset;
-webkit-text-fill-color: #f0f4f8;
caret-color: #00c9a7;
}
.auth-input:not(:-webkit-autofill) {
animation-name: autofillEnd;
animation-duration: 1ms;
}
/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 560px) {
.auth-card {
padding: 28px 20px;
gap: 22px;
}
.auth-button {
height: 50px;
font-size: 15px;
}
.auth-row {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
}