fabrica

hanna/fabrica

feat(web): card the sign-in and invite forms, centered

b21d224 · hanna committed on 2026-07-25

Wrap the login and invite-activation forms in a centered `.auth-card`
(flexbox, vertically and horizontally centered) and drop the unused
`.form-narrow` layout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed by hannaSSH key fingerprint: SHA256:4g9cWhkAAw8gwqhJUcVbSnGEvdGwklW+1Aa/fMUu59k
2 files changed · +54 −28UnifiedSplit
assets/base.css +25 −3
@@ -274,9 +274,31 @@ select:focus {
274 outline: none;274 outline: none;
275}275}
276276
277.form-narrow {277/* Centered auth card (sign in, invite activation). */
278 max-width: 24rem;278.auth-wrap {
279 margin: 3rem auto;279 display: flex;
280 flex-direction: column;
281 align-items: center;
282 justify-content: center;
283 min-height: calc(100vh - var(--fb-topbar-h) - 4rem);
284 padding: 1rem 0;
285}
286.auth-card {
287 width: 100%;
288 max-width: 22rem;
289 background: var(--fb-bg-raised);
290 border: 1px solid var(--fb-border);
291 border-radius: var(--fb-radius);
292 padding: 1.75rem;
293}
294.auth-card h1 {
295 text-align: center;
296 margin-bottom: 1rem;
297}
298.auth-card form .btn {
299 width: 100%;
300 justify-content: center;
301 margin-top: 1.25rem;
280}302}
281303
282/* ---- Flash / notices ---- */304/* ---- Flash / notices ---- */
crates/web/src/pages.rs +29 −25
@@ -165,19 +165,21 @@ pub async fn login_form(
165/// Render the login form with an optional error notice.165/// Render the login form with an optional error notice.
166fn login_body(csrf: &str, error: Option<&str>) -> Markup {166fn login_body(csrf: &str, error: Option<&str>) -> Markup {
167 html! {167 html! {
168 div class="form-narrow" {168 div class="auth-wrap" {
169 h1 { "Sign in" }169 div class="auth-card" {
170 @if let Some(err) = error {170 h1 { "Sign in" }
171 div class="notice notice-error" { (err) }171 @if let Some(err) = error {
172 }172 div class="notice notice-error" { (err) }
173 form method="post" action="/login" {173 }
174 input type="hidden" name="_csrf" value=(csrf);174 form method="post" action="/login" {
175 label for="username" { "Username" }175 input type="hidden" name="_csrf" value=(csrf);
176 input type="text" id="username" name="username" autocomplete="username" required;176 label for="username" { "Username" }
177 label for="password" { "Password" }177 input type="text" id="username" name="username" autocomplete="username" required;
178 input type="password" id="password" name="password"178 label for="password" { "Password" }
179 autocomplete="current-password" required;179 input type="password" id="password" name="password"
180 p { button class="btn btn-primary" type="submit" { "Sign in" } }180 autocomplete="current-password" required;
181 button class="btn btn-primary" type="submit" { "Sign in" }
182 }
181 }183 }
182 }184 }
183 }185 }
@@ -307,18 +309,20 @@ pub async fn invite_form(
307 let _ = invite;309 let _ = invite;
308 let (jar, chrome) = build_chrome(&state, jar, None, uri.path().to_string());310 let (jar, chrome) = build_chrome(&state, jar, None, uri.path().to_string());
309 let body = html! {311 let body = html! {
310 div class="form-narrow" {312 div class="auth-wrap" {
311 h1 { "Set your password" }313 div class="auth-card" {
312 p class="muted" { "Choose a password to activate your account." }314 h1 { "Set your password" }
313 form method="post" action={ "/invite/" (token) } {315 p class="muted" { "Choose a password to activate your account." }
314 input type="hidden" name="_csrf" value=(chrome.csrf);316 form method="post" action={ "/invite/" (token) } {
315 label for="password" { "Password" }317 input type="hidden" name="_csrf" value=(chrome.csrf);
316 input type="password" id="password" name="password"318 label for="password" { "Password" }
317 autocomplete="new-password" required minlength="8";319 input type="password" id="password" name="password"
318 label for="confirm" { "Confirm password" }320 autocomplete="new-password" required minlength="8";
319 input type="password" id="confirm" name="confirm"321 label for="confirm" { "Confirm password" }
320 autocomplete="new-password" required;322 input type="password" id="confirm" name="confirm"
321 p { button class="btn btn-primary" type="submit" { "Activate account" } }323 autocomplete="new-password" required;
324 button class="btn btn-primary" type="submit" { "Activate account" }
325 }
322 }326 }
323 }327 }
324 };328 };