From a578edaf200b1fc03bfab502e2755667f6b98e95 Mon Sep 17 00:00:00 2001 From: larssand Date: Sun, 22 Mar 2026 20:26:11 +0100 Subject: [PATCH] =?UTF-8?q?modda=20layoten=20f=C3=B6r=20obs=20overlay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.js | 187 +++++++++++++++++++++---------------------------- src/styles.css | 119 +++++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 109 deletions(-) diff --git a/src/app.js b/src/app.js index 1f6f228..d18f605 100644 --- a/src/app.js +++ b/src/app.js @@ -6472,26 +6472,30 @@ function renderOverlay() { ${ active ? ` -
-
- ${branding.logoDataUrl ? `` : ""} -
-
-

${escapeHtml(getEventName(active.eventId))}

- ${escapeHtml(getSessionTypeLabel(active.type))} - ${overlayViewMode !== "tv" ? `${escapeHtml(getStartModeLabel(active.startMode))}` : ""} - ${escapeHtml(modeLabel)} -
-

${escapeHtml(active.name)}

-

${escapeHtml(branding.brandName || "JMK RB RaceController")}

-
-
-
- - ${overlayViewMode === "obs" && obsConfig && !obsConfig.showClock ? "" : `
${overlayClock}
`} -
${escapeHtml(overlayStatusLabel)}
-
-
+ ${ + overlayViewMode === "obs" + ? "" + : `
+
+ ${branding.logoDataUrl ? `` : ""} +
+
+

${escapeHtml(getEventName(active.eventId))}

+ ${escapeHtml(getSessionTypeLabel(active.type))} + ${overlayViewMode !== "tv" ? `${escapeHtml(getStartModeLabel(active.startMode))}` : ""} + ${escapeHtml(modeLabel)} +
+

${escapeHtml(active.name)}

+

${escapeHtml(branding.brandName || "JMK RB RaceController")}

+
+
+
+ + ${overlayViewMode === "obs" && obsConfig && !obsConfig.showClock ? "" : `
${overlayClock}
`} +
${escapeHtml(overlayStatusLabel)}
+
+
` + } ${ overlayViewMode === "speaker" @@ -6646,6 +6650,8 @@ function renderObsOverlay(active, leaderboard, result, sessionTiming, branding) const readyPositionGrid = active && active.status === "ready" && normalizeStartMode(active.startMode) === "position"; const showStartGrid = obsConfig.showGrid && readyPositionGrid; const leadRow = compactRows[0] || null; + const fastestRow = + [...compactRows].filter((row) => Number.isFinite(row.bestLapMs)).sort((left, right) => left.bestLapMs - right.bestLapMs)[0] || null; const visiblePassings = getVisiblePassings(result); const elapsedOrRemaining = sessionTiming?.untimed ? formatElapsedClock(sessionTiming?.elapsedMs ?? 0) @@ -6716,104 +6722,67 @@ function renderObsOverlay(active, leaderboard, result, sessionTiming, branding) `; } + const towerRowsHtml = showStartGrid + ? getSessionGridEntries(active) + .slice(0, obsConfig.rows) + .map((entry, idx) => { + const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; + return ` +
+ ${entry.slot} +
+ ${escapeHtml(entry.name)} + ${escapeHtml(entry.meta || "-")} +
+
+ `; + }) + .join("") + : compactRows + .map((row, idx) => { + const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; + const trail = [ + obsConfig.showLaps ? `${row.laps ?? 0}L` : "", + obsConfig.showResult ? `${escapeHtml(row.resultDisplay)}` : "", + obsConfig.showBest ? `${formatLap(row.bestLapMs)}` : "", + obsConfig.showGap ? `${escapeHtml(row.gapDisplay || row.gapAhead || "-")}` : "", + ] + .filter(Boolean) + .join(""); + return ` +
+ ${idx + 1} +
+ ${escapeHtml(row.displayName || row.driverName)} + ${escapeHtml(row.teamId ? formatTeamActiveMemberLabel(row) : row.subLabel || row.transponder || "-")} +
+ ${trail ? `
${trail}
` : ""} +
+ `; + }) + .join(""); + return ` -
-
-
+
+
+
${branding.logoDataUrl ? `` : ""}

${escapeHtml(getEventName(active.eventId))}

${escapeHtml(active.name)}

-
- ${escapeHtml(getSessionTypeLabel(active.type))} - ${escapeHtml(getStartModeLabel(active.startMode))} - ${t("table.laps")}: ${leadRow?.laps || 0} - ${t("timing.total_passings")}: ${visiblePassings.length || 0} -
- ${obsConfig.showClock ? ` -
- ${elapsedOrRemaining} - ${escapeHtml(sessionTiming?.followUpActive ? t("timing.follow_up_active") : getStatusLabel(active.status))} -
- ` : ""} + ${obsConfig.showClock ? `
${elapsedOrRemaining}
` : ""}
-
-
- ${showStartGrid - ? ` -
-

${t("events.start_grid")}

- ${escapeHtml(getStartModeLabel(active.startMode))} -
- ${renderPositionGrid(active)} - ` - : obsConfig.showFastest - ? ` -
-

${t("overlay.fastest_lap")}

- ${t("overlay.mode_obs")} -
-
-
- ${t("overlay.fastest_lap")} - ${formatLap(leadRow?.bestLapMs)} -
-
${escapeHtml(leadRow?.displayName || leadRow?.driverName || "-")}
-
${escapeHtml(branding.brandName || "JMK RB RaceController")}
-
-
- ${t("events.position_grid")} - ${t("overlay.leaderboard_live")} -
- ` - : ` -
-

${escapeHtml(branding.brandName || "JMK RB RaceController")}

- ${t("overlay.mode_obs")} -
-
- ${escapeHtml(getEventName(active.eventId))} - ${escapeHtml(active.name)} -
- `} -
-
- ${compactRows.length ? compactRows.map((row, idx) => { - const posClass = idx === 0 ? "pos-1" : idx === 1 ? "pos-2" : idx === 2 ? "pos-3" : ""; - return ` -
- ${idx + 1} -
- ${escapeHtml(row.displayName || row.driverName)} - ${escapeHtml(row.teamId ? formatTeamActiveMemberLabel(row) : row.subLabel || row.transponder || "-")} -
- ${obsConfig.showLaps ? ` -
- - ${row.laps ?? 0} -
` : ""} - ${obsConfig.showResult ? ` -
- - ${escapeHtml(row.resultDisplay)} -
` : ""} - ${obsConfig.showBest ? ` -
- - ${formatLap(row.bestLapMs)} -
` : ""} - ${obsConfig.showGap ? ` -
- - ${escapeHtml(row.gapDisplay || row.gapAhead || "-")} -
` : ""} -
- `; - }).join("") : `

${t("timing.no_laps")}

`} -
+
+ ${escapeHtml(getSessionTypeLabel(active.type))} + ${escapeHtml(getStartModeLabel(active.startMode))} + ${showStartGrid ? `${t("events.start_grid")}` : ""} + ${!showStartGrid && obsConfig.showFastest ? `${t("overlay.fastest_lap")}: ${formatLap(fastestRow?.bestLapMs)}` : ""}
+
+ ${towerRowsHtml || `

${t("timing.no_laps")}

`} +
`; } diff --git a/src/styles.css b/src/styles.css index 7e6094c..e0945dd 100644 --- a/src/styles.css +++ b/src/styles.css @@ -2162,3 +2162,122 @@ select:focus { padding: 12px; } } + + +.overlay-shell-obs-layout-leaderboard { + align-items: start; +} + +.overlay-shell-obs-layout-leaderboard .overlay-obs-layout, +.overlay-shell-obs-layout-leaderboard .overlay-obs-tower { + max-width: 430px; + margin-left: auto; +} + +.overlay-obs-tower { + display: grid; + gap: 6px; +} + +.overlay-obs-tower-head, +.overlay-obs-tower-meta, +.overlay-obs-tower-standings { + border: 1px solid var(--line); + border-radius: 10px; + background: color-mix(in srgb, var(--panel) 90%, transparent); + box-shadow: var(--shadow); +} + +.overlay-obs-tower-head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 10px; +} + +.overlay-obs-tower-brand { + display: flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.overlay-obs-tower-brand h2 { + margin: 0; + font-family: Orbitron, sans-serif; + font-size: clamp(0.88rem, 1.1vw, 1.05rem); + line-height: 1.05; +} + +.overlay-obs-tower-clock { + font-family: Orbitron, sans-serif; + font-size: clamp(0.98rem, 1.2vw, 1.15rem); + line-height: 1; + white-space: nowrap; +} + +.overlay-obs-tower-meta { + display: flex; + flex-wrap: wrap; + gap: 4px 8px; + padding: 6px 10px; + color: var(--muted); + font-size: 0.58rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.overlay-obs-tower-standings { + display: grid; + gap: 4px; + padding: 6px; +} + +.overlay-obs-tower-row { + display: grid; + grid-template-columns: 24px minmax(0, 1fr) auto; + gap: 6px; + align-items: center; + padding: 5px 6px; + border-radius: 8px; + background: var(--surface-card); + border: 1px solid color-mix(in srgb, var(--line) 84%, white 16%); +} + +.overlay-obs-tower-driver { + min-width: 0; +} + +.overlay-obs-tower-driver strong { + display: block; + font-size: 0.78rem; + line-height: 1.05; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.overlay-obs-tower-driver span { + display: block; + color: var(--muted); + font-size: 0.52rem; + line-height: 1.1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.overlay-obs-tower-trail { + display: flex; + align-items: center; + gap: 6px; + justify-content: flex-end; + font-family: Orbitron, sans-serif; + font-size: 0.66rem; + white-space: nowrap; +} + +.overlay-obs-tower-trail span { + display: inline-block; +}