Toast new events into the Event Log button
When the Event Log is collapsed, a freshly-arrived event now flashes as a toast anchored above the button, then animates down and fades into it. Suppressed on the initial state load and on rollback reseeds so it only fires for genuinely new events; clicking the toast opens the log. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
- [x] The back button on the admin page should go back to the active game, not the join screen
|
- [x] The back button on the admin page should go back to the active game, not the join screen
|
||||||
|
|
||||||
- [ ] Add a toast pop-up for new events entering the event log, that then fades into the event log button
|
- [x] Add a toast pop-up for new events entering the event log, that then fades into the event log button
|
||||||
|
|
||||||
- [ ] When you get a gat, there should be a pop up to enter a description of it, as with your name
|
- [ ] When you get a gat, there should be a pop up to enter a description of it, as with your name
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,11 @@
|
|||||||
let atBottom = true;
|
let atBottom = true;
|
||||||
let error = '';
|
let error = '';
|
||||||
let timelineVersion = null;
|
let timelineVersion = null;
|
||||||
|
// Toast that briefly previews a freshly-arrived event while the log is
|
||||||
|
// collapsed, then animates down into the Event Log button.
|
||||||
|
let toast = null; // { id, message, kind }
|
||||||
|
let initialized = false; // suppresses a toast flood on the first state load
|
||||||
|
|
||||||
|
|
||||||
// Rollback buttons appear during a scene, for Admins and Deep players (server-enforced too).
|
// Rollback buttons appear during a scene, for Admins and Deep players (server-enforced too).
|
||||||
$: canRollback = state.game?.phase === 'scene'
|
$: canRollback = state.game?.phase === 'scene'
|
||||||
@@ -57,20 +62,24 @@
|
|||||||
// action from a rolled-back state truncates the abandoned future and bumps the
|
// action from a rolled-back state truncates the abandoned future and bumps the
|
||||||
// version, which tells us to drop what we'd accumulated and reseed.
|
// version, which tells us to drop what we'd accumulated and reseed.
|
||||||
const version = state.game?.rollback_timeline_version ?? 0;
|
const version = state.game?.rollback_timeline_version ?? 0;
|
||||||
|
let reset = false;
|
||||||
if (timelineVersion !== null && version !== timelineVersion) {
|
if (timelineVersion !== null && version !== timelineVersion) {
|
||||||
events = [];
|
events = [];
|
||||||
seenIds = new Set();
|
seenIds = new Set();
|
||||||
|
reset = true;
|
||||||
}
|
}
|
||||||
timelineVersion = version;
|
timelineVersion = version;
|
||||||
if (events.length === 0) {
|
if (events.length === 0) {
|
||||||
haveMore = state.events_have_more ?? polled.length >= PAGE_SIZE;
|
haveMore = state.events_have_more ?? polled.length >= PAGE_SIZE;
|
||||||
}
|
}
|
||||||
let added = false;
|
let added = false;
|
||||||
|
let newest = null;
|
||||||
for (const e of polled) {
|
for (const e of polled) {
|
||||||
if (!seenIds.has(e.id)) {
|
if (!seenIds.has(e.id)) {
|
||||||
seenIds.add(e.id);
|
seenIds.add(e.id);
|
||||||
events.push(e);
|
events.push(e);
|
||||||
added = true;
|
added = true;
|
||||||
|
newest = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (added) {
|
if (added) {
|
||||||
@@ -79,7 +88,20 @@
|
|||||||
await tick();
|
await tick();
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
// Toast only for genuinely new events (not the initial seed or a
|
||||||
|
// rollback reseed) and only while the log is closed.
|
||||||
|
if (initialized && !reset && !open && newest) {
|
||||||
|
toast = { id: newest.id, message: newest.message, kind: newest.kind || 'info' };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opening the log makes the preview redundant.
|
||||||
|
$: if (open) toast = null;
|
||||||
|
|
||||||
|
function clearToast(id) {
|
||||||
|
if (toast && toast.id === id) toast = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollToBottom() {
|
function scrollToBottom() {
|
||||||
@@ -149,6 +171,19 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="floating-event-log {open ? 'open' : ''}">
|
<div class="floating-event-log {open ? 'open' : ''}">
|
||||||
|
{#if toast && !open}
|
||||||
|
{#key toast.id}
|
||||||
|
<button
|
||||||
|
class="event-toast log-kind-{toast.kind}"
|
||||||
|
title="Open the Event Log"
|
||||||
|
on:click={toggleOpen}
|
||||||
|
on:animationend={() => clearToast(toast.id)}
|
||||||
|
>
|
||||||
|
<span class="log-icon">{KIND_ICONS[toast.kind] || KIND_ICONS.info}</span>
|
||||||
|
<span class="toast-message">{toast.message}</span>
|
||||||
|
</button>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
<button class="toggle-log-btn" on:click={toggleOpen}>
|
<button class="toggle-log-btn" on:click={toggleOpen}>
|
||||||
{open ? '⬇️ Hide Log' : '📜 Event Log'}
|
{open ? '⬇️ Hide Log' : '📜 Event Log'}
|
||||||
</button>
|
</button>
|
||||||
@@ -352,6 +387,48 @@
|
|||||||
filter: brightness(1.1);
|
filter: brightness(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast preview of a fresh event, anchored above the button */
|
||||||
|
.event-toast {
|
||||||
|
position: absolute;
|
||||||
|
bottom: calc(100% + 8px);
|
||||||
|
right: 0;
|
||||||
|
width: 320px;
|
||||||
|
max-width: 80vw;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: color-mix(in srgb, var(--bg-deep) 95%, transparent);
|
||||||
|
border: 1px solid var(--edge);
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-deep);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
cursor: pointer;
|
||||||
|
transform-origin: bottom right;
|
||||||
|
animation: event-toast-into-button 3.4s ease-in forwards;
|
||||||
|
}
|
||||||
|
.event-toast .toast-message {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
@keyframes event-toast-into-button {
|
||||||
|
0% { opacity: 0; transform: translateY(-6px) scale(0.96); }
|
||||||
|
8% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
75% { opacity: 1; transform: translateY(0) scale(1); }
|
||||||
|
100% { opacity: 0; transform: translateY(40px) scale(0.4); }
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.event-toast { animation-duration: 2.5s; animation-timing-function: linear; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Accent colors by event kind */
|
/* Accent colors by event kind */
|
||||||
.log-kind-challenge { border-left-color: var(--danger); }
|
.log-kind-challenge { border-left-color: var(--danger); }
|
||||||
.log-kind-card { border-left-color: var(--deep); }
|
.log-kind-card { border-left-color: var(--deep); }
|
||||||
|
|||||||
Reference in New Issue
Block a user