Adds a "Cards" tab to the Profile Background picker (BackgroundsView)
that selects a pattern applied to the entire user info card and the
extended profile container, in addition to the existing avatar-pad
background/stand/overlay layers.
- AvatarInfoUser/Utilities: propagate cardBackgroundId from RoomUserData.
- InfoStandWidgetUserView: stateful cardBackgroundId, applied as
.profile-card-background.card-background-{id} on the outer Column
with bg-color suppressed when active.
- UserContainerView: same class on the wrapper of the extended profile.
- BackgroundsView: 4th tab "cards" backed by cards.data config
(falls back to backgrounds.data); sends 4-id message via the
extended sendBackgroundMessage signature.
- ui-config.example: cards.data dataset (15 entries).
- BackgroundsView.css: 188 .card-background-{N} rules cloned from
background-{N} (repeat-tiled) plus 15 CSS-pattern overrides for the
provisional dataset (gradients, stripes, dots, grid, checker).
The face avatar (headOnly LayoutAvatarImageView) sits in a 63px-tall
box (44px on mobile) while sibling toolbar icons are smaller, so its
head sprite rendered visually higher than the other icons. Bumped
marginTop from 2px → 12px (desktop) and 4px → 9px (mobile) so the
head sits on the same horizontal axis as the rest of the toolbar.
Removed `absolute bottom-[60px] left-[33px]` from the inner Flex of
ToolbarMeView. The outer wrapper in ToolbarView already anchors the
popup above the face button (bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2),
so the inner pixel-perfect override was detaching it and making it float
mid-screen.
Backend (AuthHttpHandler):
- New users_remember_tokens table stores sha256 hex of the raw token
so the DB never holds a usable credential. Seed file adds the table
and a login.remember.duration.days setting (default 30).
- /api/auth/login accepts "remember": true. On success, issues a fresh
32-byte base64url token, stores the hash, returns the raw token.
- New POST /api/auth/remember: accepts the raw token, looks up by hash,
on a valid hit mints a fresh SSO ticket, rotates the token (deletes
the consumed one and issues a new one), returns both to the client.
No Turnstile - it's an automated trusted-device flow.
- /api/auth/logout also accepts rememberToken and deletes that single
row so other devices keep their tokens.
Frontend:
- LoginView: "Remember me" checkbox (key login.remember_me already in
ExternalTexts). Enabling it persists the returned rememberToken in
localStorage.nitro.remember.token.
- App.tsx: before deciding to show the login screen, try a silent POST
to /api/auth/remember with the stored token. On 200, inject the
returned ssoTicket into window.NitroConfig and proceed to the
authenticated flow; on 401, forget the token and show login.
- PurseView logout: sends the stored rememberToken in the body so the
server can delete it, and clears localStorage before reload.