diff --git a/server/src/services/replayEngineV2.js b/server/src/services/replayEngineV2.js
index d100a19f..a0fb3a45 100644
--- a/server/src/services/replayEngineV2.js
+++ b/server/src/services/replayEngineV2.js
@@ -27,7 +27,7 @@ const TARGET_FPS = Math.max(10, Number.parseInt(process.env.REPLAY_TARGET_FPS ||
const MAX_WIDTH = Math.max(320, Number.parseInt(process.env.REPLAY_MAX_WIDTH || '1280', 10));
const MAX_HEIGHT = Math.max(180, Number.parseInt(process.env.REPLAY_MAX_HEIGHT || '720', 10));
const MAX_BYTES = Math.floor(Number.parseFloat(process.env.REPLAY_MAX_OUTPUT_MB || '9.5') * 1024 * 1024);
-const SIDEBAR_WIDTH = Math.max(220, Number.parseInt(process.env.REPLAY_SIDEBAR_WIDTH || '360', 10));
+const SIDEBAR_WIDTH = Math.max(200, Number.parseInt(process.env.REPLAY_SIDEBAR_WIDTH || '290', 10));
const events = new EventEmitter();
@@ -419,7 +419,7 @@ function escapeXml(text) {
.replace(/'/g, ''');
}
-function wrapTextLines(text, maxChars = 36) {
+function wrapTextLines(text, maxChars = 28) {
const words = String(text || '').split(/\s+/).filter(Boolean);
const lines = [];
let current = '';
@@ -443,39 +443,67 @@ function renderSidebarSvg({
driverBatteryLines,
chatLines,
}) {
- const safeTitle = escapeXml(title);
+ const titleParts = wrapTextLines(title, 22).slice(0, 4).map((line) => escapeXml(line));
const statLines = driverBatteryLines.slice(0, 8).map((line) => escapeXml(line));
- const chatBlocks = chatLines.slice(0, 10).flatMap((line) => wrapTextLines(line, 34).map((wrapped) => escapeXml(wrapped)));
+ const normalizedChats = chatLines
+ .slice(-12)
+ .map((line) => wrapTextLines(line, 24).slice(0, 3).map((wrapped) => escapeXml(wrapped)));
- let y = 34;
- const titleParts = wrapTextLines(safeTitle, 26);
+ const shapes = [];
const textRows = [];
+ const pad = 12;
+ const cardX = pad;
+ const cardW = width - pad * 2;
+ let y = 12;
+
+ // Title card
+ const titleCardH = Math.max(64, 22 + titleParts.length * 24);
+ shapes.push(``);
+ let ty = y + 28;
for (const part of titleParts) {
- textRows.push(`${part}`);
- y += 34;
+ textRows.push(`${part}`);
+ ty += 22;
}
- y += 8;
- textRows.push(`Active Drivers`);
- y += 24;
- if (!statLines.length) {
- textRows.push(`No active drivers`);
- y += 22;
- } else {
- for (const line of statLines) {
- textRows.push(`${line}`);
- y += 23;
- }
+ y += titleCardH + 10;
+
+ // Drivers card
+ const driverLines = statLines.length ? statLines : ['No active drivers'];
+ const driversCardH = 28 + driverLines.length * 20 + 10;
+ shapes.push(``);
+ textRows.push(`Drivers`);
+ let dy = y + 44;
+ for (const line of driverLines) {
+ textRows.push(`${line}`);
+ dy += 20;
}
- y += 18;
- textRows.push(`Chat`);
- y += 24;
- if (!chatBlocks.length) {
- textRows.push(`No chat in replay window`);
+ y += driversCardH + 10;
+
+ // Chat card
+ const chatCardH = Math.max(80, height - y - 12);
+ shapes.push(``);
+ textRows.push(`Chat`);
+
+ let cy = y + 34;
+ const bubbleX = cardX + 10;
+ const bubbleW = cardW - 20;
+ if (!normalizedChats.length) {
+ textRows.push(`No chat in replay window`);
} else {
- for (const line of chatBlocks) {
- textRows.push(`${line}`);
- y += 22;
- if (y > height - 20) break;
+ for (let i = 0; i < normalizedChats.length; i += 1) {
+ const block = normalizedChats[i];
+ const bubbleH = 10 + block.length * 18;
+ if (cy + bubbleH + 6 > y + chatCardH - 8) break;
+ shapes.push(
+ ``,
+ );
+ let by = cy + 18;
+ for (const line of block) {
+ textRows.push(`${line}`);
+ by += 18;
+ }
+ cy += bubbleH + 6;
}
}
@@ -483,19 +511,23 @@ function renderSidebarSvg({
`;
}