A refused resume now leaves the room instead of showing a banner over a
dead board, and /play offers a fresh game there. Entering /play forgets
any stored token so it no longer races StartBotGame. An eliminated
player who reconnects stays a spectator. Held lobby actions wait for the
resume's RoomState; leaving while offline is not held. Chat clears its
draft only when the line was sent and cannot send while offline.
Also: away banners no longer announce every second; game over does not
steal focus from the chat field; the clock offset uses the lowest-RTT
pong; the history export numbers a resumed chain from its real length;
chat replay keeps existing ordinals; one .primary button class replaces
five copies; the footer links the data change list; the online heading
joins the type ramp. New component, connection and storage-guard tests.
Add AlertBanner and turnActions to replace duplicated markup and
wrappers, and share component-test setup.
Fixes: oversized dismiss, help, rules and skip-link text after the type
ramp collapse; report button hover styled as submit; chat draft lost
when the panel folds; resign/claim/kick styles leaking app-wide.
Cover the fold toggle, the unread badge counting only while folded and
clearing on open, the recount after a fold-and-reread cycle, and send/clear.
Needed a resolve.conditions fix in vite.config.js gated on process.env.VITEST
so Vitest picks svelte's client runtime instead of its SSR one for mount();
vite dev and vite build are untouched.