ui: refine auth and account screens
This commit is contained in:
@@ -6,7 +6,7 @@ import "fmt"
|
||||
import "strings"
|
||||
|
||||
templ AnimeDetails(anime jikan.Anime, currentStatus string) {
|
||||
@Layout("mal - " + anime.DisplayTitle()) {
|
||||
@Layout("mal - " + anime.DisplayTitle(), true) {
|
||||
<div class="anime-page">
|
||||
<div class="anime-main">
|
||||
<div class="anime-hero anime-surface">
|
||||
|
||||
@@ -1,50 +1,212 @@
|
||||
package templates
|
||||
|
||||
templ Login() {
|
||||
@Layout("Login") {
|
||||
<div class="login-container">
|
||||
<h2>Sign in</h2>
|
||||
<p class="login-subtitle">Enter your credentials to continue.</p>
|
||||
<form action="/login" method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username / Email</label>
|
||||
<input type="text" id="username" name="username" required placeholder="you@example.com"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required placeholder="Your password"/>
|
||||
</div>
|
||||
<button type="submit" class="login-button">Sign in</button>
|
||||
</form>
|
||||
<p class="auth-switch-row">
|
||||
Don't have an account? <a href="/register">Register</a>
|
||||
</p>
|
||||
templ Login(formError string, username string) {
|
||||
@Layout("Login", false) {
|
||||
<div class="auth-shell">
|
||||
<div class="login-container">
|
||||
<h2>Sign in</h2>
|
||||
<p class="login-subtitle">Enter your credentials to continue.</p>
|
||||
<form action="/login" method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username / Email</label>
|
||||
<input type="text" id="username" name="username" required placeholder="you@example.com" value={ username }/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required placeholder="Your password"/>
|
||||
</div>
|
||||
<button type="submit" class="login-button">Sign in</button>
|
||||
if formError != "" {
|
||||
<p class="auth-form-error" role="alert" aria-live="polite">{ formError }</p>
|
||||
}
|
||||
</form>
|
||||
<p class="auth-switch-row">
|
||||
Don't have an account? <a href="/register">Register</a>
|
||||
</p>
|
||||
<p class="auth-switch-row">
|
||||
Lost access? <a href="/recover">Recover account</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ Register() {
|
||||
@Layout("Register") {
|
||||
<div class="login-container">
|
||||
<h2>Register</h2>
|
||||
<p class="login-subtitle">Create a new account to track anime.</p>
|
||||
<form action="/register" method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username / Email</label>
|
||||
<input type="text" id="username" name="username" required placeholder="you@example.com"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required placeholder="Minimum 12 chars"/>
|
||||
</div>
|
||||
<p class="auth-password-note">
|
||||
Password must be at least 12 characters and include an uppercase letter, lowercase letter, number, and special character.
|
||||
templ Register(formError string, username string) {
|
||||
@Layout("Register", false) {
|
||||
<div class="auth-shell">
|
||||
<div class="login-container">
|
||||
<h2>Register</h2>
|
||||
<p class="login-subtitle">Create a new account to track anime.</p>
|
||||
<form action="/register" method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username / Email</label>
|
||||
<input type="text" id="username" name="username" required placeholder="you@example.com" value={ username }/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password" required placeholder="Minimum 12 chars"/>
|
||||
</div>
|
||||
<p class="auth-password-note">
|
||||
Password must be at least 12 characters and include an uppercase letter, lowercase letter, number, and special character.
|
||||
</p>
|
||||
<button type="submit" class="login-button">Create account</button>
|
||||
if formError != "" {
|
||||
<p class="auth-form-error" role="alert" aria-live="polite">{ formError }</p>
|
||||
}
|
||||
</form>
|
||||
<p class="auth-switch-row">
|
||||
Already have an account? <a href="/login">Sign in</a>
|
||||
</p>
|
||||
<button type="submit" class="login-button">Create account</button>
|
||||
</form>
|
||||
<p class="auth-switch-row">
|
||||
Already have an account? <a href="/login">Sign in</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ RegistrationRecoveryKey(recoveryKey string) {
|
||||
@Layout("Save recovery key", false) {
|
||||
<div class="auth-shell">
|
||||
<div class="login-container">
|
||||
<h2>Save your recovery key</h2>
|
||||
<p class="login-subtitle">Store this key somewhere safe. It is shown only once.</p>
|
||||
<div class="recovery-key-row">
|
||||
<p class="recovery-key-box" id="registration-recovery-key">{ recoveryKey }</p>
|
||||
<button type="button" class="recovery-copy-btn" onclick="copyRecoveryKey('registration-recovery-key', 'registration-copy-feedback')">Copy key</button>
|
||||
</div>
|
||||
<p class="auth-password-note recovery-copy-feedback" id="registration-copy-feedback" aria-live="polite"></p>
|
||||
<p class="auth-password-note">If you lose your password, this key is the only way to recover your account without email.</p>
|
||||
<p class="auth-switch-row">
|
||||
<a href="/" class="auth-primary-link">I saved it, continue</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ Recover(formError string, username string, recoveryKey string) {
|
||||
@Layout("Recover account", false) {
|
||||
<div class="auth-shell">
|
||||
<div class="login-container">
|
||||
<h2>Recover account</h2>
|
||||
<p class="login-subtitle">Enter your username, recovery key, and a new password.</p>
|
||||
<form action="/recover" method="POST" class="login-form">
|
||||
<div class="form-group">
|
||||
<label for="username">Username / Email</label>
|
||||
<input type="text" id="username" name="username" required placeholder="you@example.com" value={ username }/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="recovery_key">Recovery key</label>
|
||||
<input type="text" id="recovery_key" name="recovery_key" required value={ recoveryKey }/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">New password</label>
|
||||
<input type="password" id="new_password" name="new_password" required placeholder="Minimum 12 chars"/>
|
||||
</div>
|
||||
<button type="submit" class="login-button">Reset password</button>
|
||||
if formError != "" {
|
||||
<p class="auth-form-error" role="alert" aria-live="polite">{ formError }</p>
|
||||
}
|
||||
</form>
|
||||
<p class="auth-switch-row">
|
||||
Remembered your password? <a href="/login">Sign in</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ RecoveryComplete(newRecoveryKey string) {
|
||||
@Layout("Recovery complete", false) {
|
||||
<div class="auth-shell">
|
||||
<div class="login-container">
|
||||
<h2>Account recovered</h2>
|
||||
<p class="login-subtitle">Your password was reset and your recovery key was rotated.</p>
|
||||
<div class="recovery-key-row">
|
||||
<p class="recovery-key-box" id="recovery-complete-key">{ newRecoveryKey }</p>
|
||||
<button type="button" class="recovery-copy-btn" onclick="copyRecoveryKey('recovery-complete-key', 'recovery-complete-feedback')">Copy key</button>
|
||||
</div>
|
||||
<p class="auth-password-note recovery-copy-feedback" id="recovery-complete-feedback" aria-live="polite"></p>
|
||||
<p class="auth-password-note">Replace your old recovery key with this one.</p>
|
||||
<p class="auth-switch-row">
|
||||
<a href="/login" class="auth-primary-link">Go to login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ Account(username string, createdAt string, passwordError string, passwordSuccess string, recoveryError string, recoverySuccess string, recoveryKey string) {
|
||||
@Layout("Account", true) {
|
||||
<div class="account-page">
|
||||
<section class="account-card">
|
||||
<h2>Account</h2>
|
||||
<div class="account-meta">
|
||||
<div class="account-meta-row">
|
||||
<span class="account-meta-label">Email / Username</span>
|
||||
<span class="account-meta-value">{ username }</span>
|
||||
</div>
|
||||
<div class="account-meta-row">
|
||||
<span class="account-meta-label">Created</span>
|
||||
<span class="account-meta-value">{ createdAt }</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="account-card">
|
||||
<h3>Change password</h3>
|
||||
<form action="/account/password" method="POST" class="account-form" onsubmit="return confirmDangerAction('Change your password now?')">
|
||||
<div class="form-group">
|
||||
<label for="current_password">Current password</label>
|
||||
<input type="password" id="current_password" name="current_password" required/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="new_password">New password</label>
|
||||
<input type="password" id="new_password" name="new_password" required placeholder="Minimum 12 chars"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirm_new_password">Confirm new password</label>
|
||||
<input type="password" id="confirm_new_password" name="confirm_new_password" required/>
|
||||
</div>
|
||||
<button type="submit" class="account-submit-btn">Update password</button>
|
||||
if passwordError != "" {
|
||||
<p class="auth-form-error" role="alert" aria-live="polite">{ passwordError }</p>
|
||||
}
|
||||
if passwordSuccess != "" {
|
||||
<p class="account-success" role="status" aria-live="polite">{ passwordSuccess }</p>
|
||||
}
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="account-card">
|
||||
<h3>Recovery key</h3>
|
||||
<p class="auth-password-note">To view a new recovery key, confirm your current password. This rotates your old key.</p>
|
||||
<form action="/account/recovery-key" method="POST" class="account-form" onsubmit="return confirmDangerAction('Rotate recovery key now? Your old key will stop working.')">
|
||||
<div class="form-group">
|
||||
<label for="recovery_password">Current password</label>
|
||||
<input type="password" id="recovery_password" name="password" required/>
|
||||
</div>
|
||||
<button type="submit" class="account-submit-btn">Show new recovery key</button>
|
||||
if recoveryError != "" {
|
||||
<p class="auth-form-error" role="alert" aria-live="polite">{ recoveryError }</p>
|
||||
}
|
||||
if recoverySuccess != "" {
|
||||
<p class="account-success" role="status" aria-live="polite">{ recoverySuccess }</p>
|
||||
}
|
||||
</form>
|
||||
if recoveryKey != "" {
|
||||
<div class="recovery-key-row">
|
||||
<p class="recovery-key-box" id="account-recovery-key">{ recoveryKey }</p>
|
||||
<button type="button" class="recovery-copy-btn" onclick="copyRecoveryKey('account-recovery-key', 'account-copy-feedback')">Copy key</button>
|
||||
</div>
|
||||
<p class="auth-password-note recovery-copy-feedback" id="account-copy-feedback" aria-live="polite"></p>
|
||||
}
|
||||
</section>
|
||||
|
||||
<section class="account-card">
|
||||
<h3>Danger zone</h3>
|
||||
<form action="/logout" method="POST" class="account-form-inline" onsubmit="return confirmDangerAction('Log out of this account now?')">
|
||||
<button type="submit" class="account-logout-btn">Log out</button>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import "mal/internal/shared/ui"
|
||||
import "fmt"
|
||||
|
||||
templ Catalog() {
|
||||
@Layout("mal - catalog") {
|
||||
@Layout("mal - catalog", true) {
|
||||
<div class="catalog-grid" id="catalog-content">
|
||||
<div class="grid-full-width" hx-get="/api/catalog?page=1" hx-trigger="load" hx-swap="outerHTML">
|
||||
@ui.LoadingIndicator("Loading catalog")
|
||||
|
||||
@@ -5,7 +5,7 @@ import "mal/internal/shared/ui"
|
||||
import "fmt"
|
||||
|
||||
templ Discover() {
|
||||
@Layout("mal - discover") {
|
||||
@Layout("mal - discover", true) {
|
||||
<div class="discover-container">
|
||||
<div class="discover-header">
|
||||
<h1>Discover</h1>
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
templ Search(q string) {
|
||||
@Layout("mal - search") {
|
||||
@Layout("mal - search", true) {
|
||||
if q != "" {
|
||||
<div id="loading" class="htmx-indicator">
|
||||
@ui.LoadingIndicator("Searching...")
|
||||
|
||||
@@ -2,7 +2,7 @@ package templates
|
||||
|
||||
import "mal/internal/shared/ui/icons"
|
||||
|
||||
templ Layout(title string) {
|
||||
templ Layout(title string, showHeader bool) {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -15,30 +15,34 @@ templ Layout(title string) {
|
||||
<script src="/static/js/discover.js" defer></script>
|
||||
<script src="/static/js/anime.js" defer></script>
|
||||
<script src="/static/js/timezone.js" defer></script>
|
||||
<script src="/static/js/auth.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="header-top">
|
||||
<div class="header-left">
|
||||
<a href="/" class="logo" aria-label="mal logo">
|
||||
@icons.LogoIcon("logo-svg")
|
||||
</a>
|
||||
<div class="nav">
|
||||
<a href="/">Catalog</a>
|
||||
<a href="/discover">Discover</a>
|
||||
<a href="/notifications">Notifications</a>
|
||||
<a href="/watchlist">Watchlist</a>
|
||||
if showHeader {
|
||||
<header>
|
||||
<div class="header-top">
|
||||
<div class="header-left">
|
||||
<a href="/" class="logo" aria-label="mal logo">
|
||||
@icons.LogoIcon("logo-svg")
|
||||
</a>
|
||||
<div class="nav">
|
||||
<a href="/">Catalog</a>
|
||||
<a href="/discover">Discover</a>
|
||||
<a href="/notifications">Notifications</a>
|
||||
<a href="/watchlist">Watchlist</a>
|
||||
<a href="/account">Account</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-search-wrapper">
|
||||
<form action="/search" method="GET" class="header-search" id="search-form">
|
||||
<input type="text" id="search-input" name="q" class="search-input" placeholder="Search anime..." autocomplete="off"/>
|
||||
<div id="search-dropdown" class="search-dropdown"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-search-wrapper">
|
||||
<form action="/search" method="GET" class="header-search" id="search-form">
|
||||
<input type="text" id="search-input" name="q" class="search-input" placeholder="Search anime..." autocomplete="off"/>
|
||||
<div id="search-dropdown" class="search-dropdown"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
</header>
|
||||
}
|
||||
<main class={ "main-content", templ.KV("auth-main", !showHeader) }>
|
||||
{ children... }
|
||||
</main>
|
||||
<script src="/static/js/search.js"></script>
|
||||
|
||||
@@ -11,7 +11,7 @@ type WatchingAnimeWithDetails struct {
|
||||
}
|
||||
|
||||
templ Notifications(watching []WatchingAnimeWithDetails, activeTab string) {
|
||||
@Layout("mal - notifications") {
|
||||
@Layout("mal - notifications", true) {
|
||||
<div class="notifications-page">
|
||||
<h1>Notifications</h1>
|
||||
<div class="status-tabs">
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
templ Watchlist(entries []database.GetUserWatchListRow, layout string, currentStatus string, sortBy string, sortOrder string) {
|
||||
@Layout("My Watchlist") {
|
||||
@Layout("My Watchlist", true) {
|
||||
<div class="watchlist-header">
|
||||
<div class="watchlist-heading">
|
||||
<h2>Watchlist</h2>
|
||||
|
||||
@@ -256,6 +256,14 @@ main {
|
||||
padding: var(--space-5) var(--space-4) var(--space-8);
|
||||
}
|
||||
|
||||
.auth-main {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -427,26 +435,31 @@ main {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-shell {
|
||||
width: min(560px, 100%);
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: min(440px, 100%);
|
||||
margin: 7vh auto;
|
||||
padding: var(--space-5);
|
||||
width: min(560px, 100%);
|
||||
margin: 0 auto;
|
||||
padding: var(--space-6);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.login-container h2 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
margin: var(--space-2) 0 var(--space-4);
|
||||
margin: var(--space-3) 0 var(--space-5);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
@@ -456,20 +469,25 @@ main {
|
||||
|
||||
.form-group label {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
height: 34px;
|
||||
border: none;
|
||||
background: var(--surface-input);
|
||||
height: 40px;
|
||||
border: 1px solid transparent;
|
||||
background: var(--surface-search);
|
||||
color: var(--text);
|
||||
padding: 0 var(--space-2);
|
||||
padding: 0 var(--space-3);
|
||||
font: inherit;
|
||||
transition: border-color 120ms ease, background-color 120ms ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
background: var(--surface-input-focus);
|
||||
border-color: var(--surface-search-focus-border);
|
||||
}
|
||||
|
||||
.form-group input:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.auth-password-note {
|
||||
@@ -479,12 +497,164 @@ main {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.auth-form-error {
|
||||
margin: var(--space-2) 0 0;
|
||||
color: var(--danger);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.recovery-key-box {
|
||||
margin: 0;
|
||||
padding: var(--space-3);
|
||||
background: var(--surface-search);
|
||||
border: 1px solid var(--surface-search-focus-border);
|
||||
color: var(--text);
|
||||
word-break: break-all;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.recovery-key-row {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.recovery-copy-btn {
|
||||
justify-self: start;
|
||||
min-width: 0;
|
||||
padding: 0.42rem 0.72rem;
|
||||
border: 1px solid var(--surface-search-focus-border);
|
||||
background: var(--surface-search);
|
||||
color: var(--text);
|
||||
border-radius: 0;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recovery-copy-btn:hover {
|
||||
background: var(--surface-input-focus);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.recovery-copy-btn:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.recovery-copy-feedback {
|
||||
margin-top: var(--space-2);
|
||||
min-height: 1.1rem;
|
||||
}
|
||||
|
||||
.auth-primary-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 40px;
|
||||
padding: 0 var(--space-4);
|
||||
border: 1px solid var(--surface-search-focus-border);
|
||||
background: var(--surface-search);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-primary-link:hover {
|
||||
background: var(--panel-soft);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-primary-link:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.account-page {
|
||||
width: min(720px, 100%);
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
gap: var(--space-4);
|
||||
}
|
||||
|
||||
.account-card {
|
||||
background: var(--panel);
|
||||
padding: var(--space-5);
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.account-card h2,
|
||||
.account-card h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.account-meta {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.account-meta-row {
|
||||
display: grid;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.account-meta-label {
|
||||
color: var(--text-faint);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.account-meta-value {
|
||||
color: var(--text);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.account-form {
|
||||
display: grid;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.account-form .form-group input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.account-submit-btn,
|
||||
.account-logout-btn {
|
||||
height: 40px;
|
||||
border: 1px solid var(--surface-search-focus-border);
|
||||
background: var(--surface-search);
|
||||
color: var(--text);
|
||||
padding: 0 var(--space-4);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.account-submit-btn:hover,
|
||||
.account-logout-btn:hover {
|
||||
background: var(--panel-soft);
|
||||
}
|
||||
|
||||
.account-submit-btn:focus-visible,
|
||||
.account-logout-btn:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.account-success {
|
||||
margin: 0;
|
||||
color: var(--accent);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.account-form-inline {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
height: 34px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
background: var(--accent);
|
||||
color: var(--text-on-accent);
|
||||
font-size: 0.84rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -494,10 +664,10 @@ main {
|
||||
}
|
||||
|
||||
.auth-switch-row {
|
||||
margin: var(--space-4) 0 0;
|
||||
margin: var(--space-5) 0 0;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.auth-switch-row a {
|
||||
|
||||
19
static/js/auth.js
Normal file
19
static/js/auth.js
Normal file
@@ -0,0 +1,19 @@
|
||||
function copyRecoveryKey(keyElementId, feedbackElementId) {
|
||||
var keyElement = document.getElementById(keyElementId)
|
||||
var feedbackElement = document.getElementById(feedbackElementId)
|
||||
|
||||
if (!keyElement || !feedbackElement) {
|
||||
return
|
||||
}
|
||||
|
||||
var key = keyElement.textContent || ''
|
||||
navigator.clipboard.writeText(key).then(function () {
|
||||
feedbackElement.textContent = 'Recovery key copied.'
|
||||
}).catch(function () {
|
||||
feedbackElement.textContent = 'Copy failed. Select and copy manually.'
|
||||
})
|
||||
}
|
||||
|
||||
function confirmDangerAction(message) {
|
||||
return window.confirm(message)
|
||||
}
|
||||
Reference in New Issue
Block a user