diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte
index efbaad8..1cd1ade 100644
--- a/packages/web/src/App.svelte
+++ b/packages/web/src/App.svelte
@@ -2958,7 +2958,7 @@
{:else}
{#each net.log as line, i (i)}
- {line.text}
+ {line.text}
{#if line.notable && line.turn !== null && prefs.instantReplay && !net.spectating}
@@ -3848,6 +3848,14 @@
min-height: 1.55rem;
}
.slip.creature-note { background: #e7edda; border-color: #7d8a5a; }
+ .score-row.active {
+ font-weight: 700;
+ background: rgba(58, 47, 31, 0.08);
+ border-left: 3px solid #3a2f1f;
+ margin-left: -0.45rem;
+ padding-left: 0.3rem;
+ border-radius: 2px;
+ }
.score-row.active .score-name { font-weight: 700; }
.score-temper {
font-family: "Caveat", cursive;
@@ -3916,6 +3924,17 @@
box-shadow: 0 2px 7px rgba(0, 0, 0, 0.45);
}
.chronicle div + div { margin-top: 0.05rem; }
+ /* the acting wizard's color, so one player's lines can be picked out at a glance */
+ .log-mark {
+ display: inline-block;
+ width: 0.55em;
+ height: 0.55em;
+ margin-right: 0.45em;
+ border-radius: 2px;
+ border: 1px solid rgba(58, 47, 31, 0.35);
+ vertical-align: 0.05em;
+ }
+ .log-mark.blank { border-color: transparent; }
.moment-eye {
background: none;
border: none;
diff --git a/packages/web/src/net.svelte.ts b/packages/web/src/net.svelte.ts
index 187049a..2469518 100644
--- a/packages/web/src/net.svelte.ts
+++ b/packages/web/src/net.svelte.ts
@@ -234,6 +234,22 @@ export interface LogLine {
text: string;
turn: number | null;
notable: boolean;
+ /** The wizard acting, for the color mark beside the line: whoever
+ * cast, struck, walked, or countered — otherwise the turn's owner. */
+ actor?: string;
+}
+
+/** Events whose `player` is the one acting rather than the one acted on. */
+const ACTING = new Set([
+ "moved", "warpStepped", "jumpedPit", "counteractionPlayed", "treasurePickedUp", "treasureDropped",
+ "numberPlayedForMovement", "cardsDiscarded", "turnStarted", "turnEnded", "doorUnlocked", "madDash",
+ "climbedFromPit", "spellTrapped",
+]);
+function actorOf(e: GameEvent, turnOwner: string | null): string | undefined {
+ const any = e as unknown as Record;
+ for (const k of ["caster", "attacker", "by", "owner"]) if (typeof any[k] === "string") return any[k] as string;
+ if (ACTING.has(e.type) && typeof any.player === "string") return any.player as string;
+ return turnOwner ?? undefined;
}
/** The boundaries the turn count walks — mirrored by the server's
@@ -305,6 +321,8 @@ class Net {
* socket that never fires onclose; the watchdog closes it by hand so
* the reconnect loop (and the masthead banner) actually engage. */
private lastHeard = Date.now();
+ /** Whose turn the chronicle is in, for the mark on lines with no actor of their own. */
+ private turnOwner: string | null = null;
private watchdog: ReturnType | null = null;
roomId = $state(null);
players = $state([]);
@@ -510,6 +528,7 @@ class Net {
this.openingRolls = { rolls: e.dieRolls, first: e.firstPlayer, players: e.players };
}
if (TURN_BOUNDARY.has(e.type)) this.turnCounter++;
+ if (e.type === "turnStarted") this.turnOwner = e.player;
const line = humanize(e);
if (line) {
// Every turn wears its eye, on the header line that opens
@@ -517,7 +536,7 @@ class Net {
// winning line carries its own besides.
const notable = this.turnCounter >= 0 &&
(e.type === "gameWon" || TURN_BOUNDARY.has(e.type));
- this.log = [...this.log, { text: line, turn: this.turnCounter >= 0 ? this.turnCounter : null, notable }];
+ this.log = [...this.log, { text: line, turn: this.turnCounter >= 0 ? this.turnCounter : null, notable, actor: actorOf(e, this.turnOwner) }];
}
}
if (talk > 0) {
@@ -545,7 +564,7 @@ class Net {
break;
}
case "chat": {
- this.log = [...this.log, { text: `\u{1F4AC} ${msg.player}: ${msg.text}`, turn: null, notable: false }];
+ this.log = [...this.log, { text: `\u{1F4AC} ${msg.player}: ${msg.text}`, turn: null, notable: false, actor: msg.player }];
this.chatCount += 1;
if (this.roomId) this.markChatSeen();
break;