mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
💄 style(matrix-web): redesign login page to match central auth UI
- Add dark/light theme toggle - Glass-morphic form card with backdrop blur - Centered logo with app name and subtitle - Custom styled inputs with icons and labels - Violet accent color matching Matrix branding - Entrance animations (fadeInUp, fadeInScale) - Shake animation on error - Success pulse animation on login - Mobile-optimized layout - Reduced motion support
This commit is contained in:
parent
f2cd8621cb
commit
dff153ca1e
6 changed files with 930 additions and 616 deletions
|
|
@ -8,6 +8,14 @@
|
|||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex min-h-screen items-center justify-center bg-base-100 p-4">
|
||||
<div class="auth-layout">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.auth-layout {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@
|
|||
HardDrive,
|
||||
User,
|
||||
Lock,
|
||||
WarningCircle,
|
||||
Warning,
|
||||
SignIn,
|
||||
Sun,
|
||||
Moon,
|
||||
Check,
|
||||
ChatCircle,
|
||||
} from '@manacore/shared-icons';
|
||||
|
||||
// Form state
|
||||
|
|
@ -25,12 +29,43 @@
|
|||
let checkingServer = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let serverValid = $state<boolean | null>(null);
|
||||
let showSuccess = $state(false);
|
||||
let shakeError = $state(false);
|
||||
|
||||
// Theme state
|
||||
let userThemePreference = $state<'light' | 'dark' | null>(null);
|
||||
let systemIsDark = $state(
|
||||
typeof window !== 'undefined'
|
||||
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
: true
|
||||
);
|
||||
|
||||
let isDark = $derived(
|
||||
userThemePreference !== null ? userThemePreference === 'dark' : systemIsDark
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
systemIsDark = mediaQuery.matches;
|
||||
const listener = (e: MediaQueryListEvent) => (systemIsDark = e.matches);
|
||||
mediaQuery.addEventListener('change', listener);
|
||||
return () => mediaQuery.removeEventListener('change', listener);
|
||||
}
|
||||
});
|
||||
|
||||
function toggleTheme() {
|
||||
if (userThemePreference === null) {
|
||||
userThemePreference = systemIsDark ? 'light' : 'dark';
|
||||
} else {
|
||||
userThemePreference = userThemePreference === 'dark' ? 'light' : 'dark';
|
||||
}
|
||||
}
|
||||
|
||||
// SSO Login via Mana Core
|
||||
function handleSSOLogin() {
|
||||
if (!browser) return;
|
||||
loadingSSO = true;
|
||||
// Redirect to Matrix SSO endpoint which will redirect to Mana Core Auth
|
||||
const redirectUrl = encodeURIComponent(window.location.origin + '/chat');
|
||||
window.location.href = `https://matrix.mana.how/_matrix/client/v3/login/sso/redirect/oidc-manacore?redirectUrl=${redirectUrl}`;
|
||||
}
|
||||
|
|
@ -63,17 +98,23 @@
|
|||
checkingServer = false;
|
||||
|
||||
if (!result.ok) {
|
||||
error = `Server check failed: ${result.error}`;
|
||||
setError(`Server nicht erreichbar: ${result.error}`);
|
||||
} else {
|
||||
error = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setError(message: string) {
|
||||
error = message;
|
||||
shakeError = true;
|
||||
setTimeout(() => (shakeError = false), 500);
|
||||
}
|
||||
|
||||
async function handleLogin(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!username.trim() || !password.trim()) {
|
||||
error = 'Please enter username and password';
|
||||
setError('Bitte Benutzername und Passwort eingeben');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -83,54 +124,90 @@
|
|||
const result = await loginWithPassword(homeserver, username, password);
|
||||
|
||||
if (result.success && result.credentials) {
|
||||
showSuccess = true;
|
||||
const initialized = await matrixStore.initialize(result.credentials);
|
||||
|
||||
if (initialized) {
|
||||
goto('/chat');
|
||||
setTimeout(() => goto('/chat'), 600);
|
||||
} else {
|
||||
error = matrixStore.error || 'Failed to initialize Matrix client';
|
||||
showSuccess = false;
|
||||
setError(matrixStore.error || 'Matrix Client konnte nicht initialisiert werden');
|
||||
}
|
||||
} else {
|
||||
error = result.error || 'Login failed';
|
||||
setError(result.error || 'Anmeldung fehlgeschlagen');
|
||||
}
|
||||
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="card w-full max-w-md bg-base-200 shadow-xl">
|
||||
<div class="card-body">
|
||||
<!-- Header -->
|
||||
<div class="mb-4 text-center">
|
||||
<h1 class="text-2xl font-bold">Mana Matrix</h1>
|
||||
<p class="text-sm text-base-content/60">Sign in to your Matrix account</p>
|
||||
<svelte:head>
|
||||
<title>Login - Mana Matrix</title>
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
class="page-container"
|
||||
class:dark={isDark}
|
||||
class:light={!isDark}
|
||||
>
|
||||
<!-- Theme Toggle -->
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggleTheme}
|
||||
class="theme-toggle"
|
||||
aria-label={isDark ? 'Zu Light Mode wechseln' : 'Zu Dark Mode wechseln'}
|
||||
>
|
||||
{#if isDark}
|
||||
<Sun size={20} weight="bold" />
|
||||
{:else}
|
||||
<Moon size={20} weight="bold" />
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- Logo Section -->
|
||||
<div class="logo-section">
|
||||
<div
|
||||
class="logo-button"
|
||||
class:success-pulse={showSuccess}
|
||||
>
|
||||
{#if showSuccess}
|
||||
<Check size={55} class="text-green-500" />
|
||||
{:else}
|
||||
<ChatCircle size={55} weight="duotone" class="text-violet-500" />
|
||||
{/if}
|
||||
</div>
|
||||
<h1 class="app-name">Mana Matrix</h1>
|
||||
<p class="app-subtitle">Sichere Matrix-Kommunikation</p>
|
||||
</div>
|
||||
|
||||
<!-- Form Section -->
|
||||
<div class="form-section">
|
||||
<div class="form-card" class:shake={shakeError}>
|
||||
<div class="form-header">
|
||||
<h2 class="form-title">Anmelden</h2>
|
||||
<p class="form-subtitle">Mit deinem Matrix-Konto anmelden</p>
|
||||
</div>
|
||||
|
||||
<!-- Error Alert -->
|
||||
{#if error}
|
||||
<div class="alert alert-error mb-4">
|
||||
<WarningCircle class="h-5 w-5" />
|
||||
<span>{error}</span>
|
||||
<div class="error-message" role="alert">
|
||||
<Warning size={18} class="text-red-500 shrink-0" />
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Login Form -->
|
||||
<form onsubmit={handleLogin} class="space-y-4">
|
||||
<form onsubmit={handleLogin}>
|
||||
<!-- Homeserver -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="homeserver">
|
||||
<span class="label-text flex items-center gap-2">
|
||||
<HardDrive class="h-4 w-4" />
|
||||
<div class="input-group">
|
||||
<label for="homeserver" class="input-label">
|
||||
<HardDrive size={16} />
|
||||
Homeserver
|
||||
</span>
|
||||
{#if checkingServer}
|
||||
<span class="label-text-alt">
|
||||
<CircleNotch class="h-4 w-4 animate-spin" />
|
||||
</span>
|
||||
<CircleNotch size={14} class="animate-spin ml-auto" />
|
||||
{:else if serverValid === true}
|
||||
<span class="label-text-alt text-success">Connected</span>
|
||||
<span class="ml-auto text-green-500 text-xs">Verbunden</span>
|
||||
{:else if serverValid === false}
|
||||
<span class="label-text-alt text-error">Unreachable</span>
|
||||
<span class="ml-auto text-red-500 text-xs">Nicht erreichbar</span>
|
||||
{/if}
|
||||
</label>
|
||||
<input
|
||||
|
|
@ -138,7 +215,7 @@
|
|||
type="text"
|
||||
bind:value={homeserver}
|
||||
onblur={validateServer}
|
||||
class="input input-bordered"
|
||||
class="input-field"
|
||||
class:input-success={serverValid === true}
|
||||
class:input-error={serverValid === false}
|
||||
placeholder="matrix.org"
|
||||
|
|
@ -147,94 +224,548 @@
|
|||
</div>
|
||||
|
||||
<!-- Username -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="username">
|
||||
<span class="label-text flex items-center gap-2">
|
||||
<User class="h-4 w-4" />
|
||||
Username
|
||||
</span>
|
||||
<div class="input-group">
|
||||
<label for="username" class="input-label">
|
||||
<User size={16} />
|
||||
Benutzername
|
||||
</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
bind:value={username}
|
||||
oninput={handleUsernameInput}
|
||||
class="input input-bordered"
|
||||
placeholder="@user:matrix.org or username"
|
||||
class="input-field"
|
||||
placeholder="@user:matrix.org oder username"
|
||||
disabled={loading}
|
||||
autocomplete="username"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Password -->
|
||||
<div class="form-control">
|
||||
<label class="label" for="password">
|
||||
<span class="label-text flex items-center gap-2">
|
||||
<Lock class="h-4 w-4" />
|
||||
Password
|
||||
</span>
|
||||
<div class="input-group">
|
||||
<label for="password" class="input-label">
|
||||
<Lock size={16} />
|
||||
Passwort
|
||||
</label>
|
||||
<div class="relative">
|
||||
<div class="input-wrapper">
|
||||
<input
|
||||
id="password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
bind:value={password}
|
||||
class="input input-bordered w-full pr-12"
|
||||
placeholder="Your password"
|
||||
class="input-field has-icon"
|
||||
placeholder="Dein Passwort"
|
||||
disabled={loading}
|
||||
autocomplete="current-password"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-base-content/50 hover:text-base-content"
|
||||
class="password-toggle"
|
||||
onclick={() => (showPassword = !showPassword)}
|
||||
tabindex={-1}
|
||||
aria-label={showPassword ? 'Passwort verbergen' : 'Passwort anzeigen'}
|
||||
>
|
||||
{#if showPassword}
|
||||
<EyeSlash class="h-5 w-5" />
|
||||
<EyeSlash size={20} />
|
||||
{:else}
|
||||
<Eye class="h-5 w-5" />
|
||||
<Eye size={20} />
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit" class="btn btn-primary w-full" disabled={loading}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || showSuccess}
|
||||
class="submit-button"
|
||||
>
|
||||
{#if loading}
|
||||
<CircleNotch class="h-5 w-5 animate-spin" />
|
||||
Signing in...
|
||||
<CircleNotch size={20} class="animate-spin" />
|
||||
<span>Anmelden...</span>
|
||||
{:else if showSuccess}
|
||||
<Check size={20} />
|
||||
<span>Erfolgreich!</span>
|
||||
{:else}
|
||||
Sign In
|
||||
<SignIn size={20} />
|
||||
<span>Anmelden</span>
|
||||
{/if}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="divider my-4 text-base-content/40">OR</div>
|
||||
<div class="divider">
|
||||
<span>oder</span>
|
||||
</div>
|
||||
|
||||
<!-- SSO Login -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline w-full gap-2"
|
||||
class="sso-button"
|
||||
onclick={handleSSOLogin}
|
||||
disabled={loadingSSO}
|
||||
>
|
||||
{#if loadingSSO}
|
||||
<CircleNotch class="h-5 w-5 animate-spin" />
|
||||
Redirecting...
|
||||
<CircleNotch size={20} class="animate-spin" />
|
||||
<span>Weiterleiten...</span>
|
||||
{:else}
|
||||
<SignIn class="h-5 w-5" />
|
||||
Sign in with Mana Core
|
||||
<SignIn size={20} />
|
||||
<span>Mit Mana Core anmelden</span>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-4 text-center text-sm text-base-content/60">
|
||||
<p>
|
||||
Don't have an account?
|
||||
<a href="/register" class="link link-primary">Register</a>
|
||||
<p class="register-link">
|
||||
Noch kein Konto?
|
||||
<a href="/register">Registrieren</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
width: 100%;
|
||||
background-color: #121212;
|
||||
}
|
||||
|
||||
.page-container.light {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.light .theme-toggle {
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.light .theme-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.logo-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 3rem 1rem 1.5rem;
|
||||
animation: fadeInScale 0.5s ease-out both;
|
||||
}
|
||||
|
||||
.logo-button {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #8b5cf6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||
background-color: #000;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.light .logo-button {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.light .app-name {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.app-subtitle {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.light .app-subtitle {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.form-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 1rem 1rem 2rem;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||||
}
|
||||
|
||||
.light .form-card {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.form-header {
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.form-subtitle {
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.light .form-title {
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
.light .form-subtitle {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
color: #ef4444;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.light .input-label {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
padding: 0 1rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.light .input-field {
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.input-field:focus {
|
||||
outline: none;
|
||||
border-color: #8b5cf6;
|
||||
box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
|
||||
.input-field.input-success {
|
||||
border-color: #22c55e;
|
||||
}
|
||||
|
||||
.input-field.input-error {
|
||||
border-color: #ef4444;
|
||||
}
|
||||
|
||||
.input-field.has-icon {
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.input-field::placeholder {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.light .input-field::placeholder {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.password-toggle {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.light .password-toggle {
|
||||
color: rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.password-toggle:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
margin-top: 1rem;
|
||||
border: 2px solid #8b5cf6;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, rgba(139, 92, 246, 0.3), rgba(124, 58, 237, 0.3));
|
||||
}
|
||||
|
||||
.light .submit-button {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.submit-button:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, rgba(139, 92, 246, 0.5), rgba(124, 58, 237, 0.5));
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.submit-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 1.25rem 0;
|
||||
}
|
||||
|
||||
.divider::before,
|
||||
.divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: currentColor;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.divider span {
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.light .divider span {
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sso-button {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.light .sso-button {
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.sso-button:hover:not(:disabled) {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.light .sso-button:hover:not(:disabled) {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sso-button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.register-link {
|
||||
text-align: center;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 1.25rem;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.light .register-link {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.register-link a {
|
||||
color: #8b5cf6;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.register-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInScale {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
||||
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
||||
}
|
||||
|
||||
.shake {
|
||||
animation: shake 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes success-pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
}
|
||||
|
||||
.success-pulse {
|
||||
animation: success-pulse 0.6s ease-in-out;
|
||||
border-color: #22c55e !important;
|
||||
}
|
||||
|
||||
/* Spin animation for loading */
|
||||
:global(.animate-spin) {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 480px) {
|
||||
.logo-section {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.logo-button {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Reduced motion */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.logo-section,
|
||||
.form-card,
|
||||
.shake,
|
||||
.success-pulse {
|
||||
animation: none;
|
||||
}
|
||||
* {
|
||||
transition-duration: 0.01ms !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
"@nestjs/core": "^10.0.0 || ^11.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@manacore/shared-tsconfig": "workspace:*",
|
||||
"@types/node": "^22.10.2",
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
{
|
||||
"extends": "@manacore/shared-tsconfig/nestjs"
|
||||
"extends": "@manacore/shared-tsconfig/nestjs",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
727
pnpm-lock.yaml
generated
727
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue