Polish: plain unfound word-search chips + no signed-out flash on refresh

- Word Search: unfound words were tinted (accent-soft background) like found
  ones, so the remaining words were hard to spot as the board filled. Unfound
  chips are now plain (transparent + a light outline); found words keep their
  grid colour. Easy to see what's left.
- Auth: a refresh briefly flashed the signed-out header until /api/auth/me
  returned. Now the last-known user is cached and hydrated immediately, so the
  signed-in UI paints at once; the session is still revalidated every load (a
  stale/expired one corrects within a beat) and the cache is cleared on logout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-12 12:21:43 -04:00
parent 7ffcc0be30
commit 39d682f353
2 changed files with 23 additions and 2 deletions
+18 -1
View File
@@ -2,7 +2,22 @@
import { SvelteSet } from 'svelte/reactivity';
import { getJSON, postJSON, delJSON } from './api.js';
export const auth = $state({ user: null, ready: false });
// Cache the last-known user so a refresh paints the signed-in UI immediately
// instead of flashing "signed out" for the /api/auth/me round-trip. The session
// is still revalidated on every load (see refresh) — a stale/expired one just
// corrects within a beat instead of greeting you logged-out every time.
const AUTH_CACHE = 'goodnews:auth_user';
function _loadUser() {
try { return JSON.parse(localStorage.getItem(AUTH_CACHE) || 'null'); } catch { return null; }
}
function _saveUser(u) {
try {
if (u) localStorage.setItem(AUTH_CACHE, JSON.stringify(u));
else localStorage.removeItem(AUTH_CACHE);
} catch { /* ignore */ }
}
export const auth = $state({ user: _loadUser(), ready: false });
export const savedIds = new SvelteSet(); // reactive set of saved article ids
export const followKeys = new SvelteSet(); // reactive set of "kind:value" follows
@@ -16,6 +31,7 @@ export async function refresh() {
auth.user = null;
} finally {
auth.ready = true;
_saveUser(auth.user);
}
if (auth.user) { await loadSaved(); await loadFollows(); }
else { savedIds.clear(); followKeys.clear(); }
@@ -99,6 +115,7 @@ export async function logout() {
// e.g. after delete-account or sign-out-everywhere).
export function clearLocal() {
auth.user = null;
_saveUser(null);
savedIds.clear();
followKeys.clear();
}
@@ -221,8 +221,12 @@
.plabel { text-transform: uppercase; letter-spacing: 0.12em; font-size: 0.62rem; font-family: var(--label);
color: var(--muted); text-align: center; margin: 0 0 10px; }
.words { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; padding: 0; margin: 0; }
/* Unfound words are plain (transparent + outline) so it's easy to see what's
left as the board fills; found words get their grid colour via inline style. */
.words li { font-family: var(--label); font-size: 0.82rem; letter-spacing: 0.04em; color: var(--ink);
padding: 4px 11px; border-radius: 999px; background: var(--accent-soft); transition: background 0.2s ease; }
padding: 4px 11px; border-radius: 999px; background: transparent;
border: 1px solid var(--line); transition: background 0.2s ease, border-color 0.2s ease; }
.words li.got { border-color: transparent; }
.hint { text-align: center; color: var(--muted); font-size: 0.84rem; margin: 0; }
.result { text-align: center; }
.rmark { font-family: var(--serif); font-style: italic; color: var(--accent-deep); font-size: 1.2rem; margin: 0 0 12px; }