fix team lag
This commit is contained in:
204
src/app.js
204
src/app.js
@@ -220,6 +220,7 @@ const TRANSLATIONS = {
|
|||||||
"events.no_teams": "Inga lag skapade ännu.",
|
"events.no_teams": "Inga lag skapade ännu.",
|
||||||
"events.team_standings": "Lagställning",
|
"events.team_standings": "Lagställning",
|
||||||
"events.no_team_results": "Inga teamresultat ännu.",
|
"events.no_team_results": "Inga teamresultat ännu.",
|
||||||
|
"events.edit_team": "Redigera lag",
|
||||||
"events.add_session": "Lägg till session",
|
"events.add_session": "Lägg till session",
|
||||||
"events.set_active": "Sätt aktiv",
|
"events.set_active": "Sätt aktiv",
|
||||||
"events.assignments": "Tilldelningar",
|
"events.assignments": "Tilldelningar",
|
||||||
@@ -277,6 +278,7 @@ const TRANSLATIONS = {
|
|||||||
"timing.open_speaker_overlay": "Speaker overlay",
|
"timing.open_speaker_overlay": "Speaker overlay",
|
||||||
"timing.open_results_overlay": "Result overlay",
|
"timing.open_results_overlay": "Result overlay",
|
||||||
"timing.open_tv_overlay": "TV overlay",
|
"timing.open_tv_overlay": "TV overlay",
|
||||||
|
"timing.open_team_overlay": "Team overlay",
|
||||||
"timing.close_details": "Stang",
|
"timing.close_details": "Stang",
|
||||||
"timing.detail_title": "Leaderboard-detaljer",
|
"timing.detail_title": "Leaderboard-detaljer",
|
||||||
"timing.lap_history": "Varvhistorik",
|
"timing.lap_history": "Varvhistorik",
|
||||||
@@ -479,12 +481,16 @@ const TRANSLATIONS = {
|
|||||||
"overlay.mode_speaker": "Speaker",
|
"overlay.mode_speaker": "Speaker",
|
||||||
"overlay.mode_results": "Resultat",
|
"overlay.mode_results": "Resultat",
|
||||||
"overlay.mode_tv": "TV",
|
"overlay.mode_tv": "TV",
|
||||||
|
"overlay.mode_team": "Team",
|
||||||
"overlay.fastest_lap": "Snabbaste varv",
|
"overlay.fastest_lap": "Snabbaste varv",
|
||||||
"overlay.fullscreen": "Fullscreen",
|
"overlay.fullscreen": "Fullscreen",
|
||||||
"overlay.leaderboard_live": "Live leaderboard",
|
"overlay.leaderboard_live": "Live leaderboard",
|
||||||
"overlay.rotating_panel": "Displaypanel",
|
"overlay.rotating_panel": "Displaypanel",
|
||||||
"overlay.next_predicted_lap": "Nästa varv",
|
"overlay.next_predicted_lap": "Nästa varv",
|
||||||
"overlay.event_markers": "Eventmarkörer",
|
"overlay.event_markers": "Eventmarkörer",
|
||||||
|
"overlay.team_battle": "Lagkamp",
|
||||||
|
"overlay.active_member": "Aktiv förare/bil",
|
||||||
|
"overlay.top_three": "Topp 3",
|
||||||
"guide.host_title": "Hur Managed AMMC körs",
|
"guide.host_title": "Hur Managed AMMC körs",
|
||||||
"guide.host_1": "1. AMMC körs alltid på samma maskin som `npm start` eller `node server.js` körs på.",
|
"guide.host_1": "1. AMMC körs alltid på samma maskin som `npm start` eller `node server.js` körs på.",
|
||||||
"guide.host_2": "2. Om du bara surfar in från en laptop/webbläsare startas ingen process där. Webbläsaren styr bara backend via HTTP.",
|
"guide.host_2": "2. Om du bara surfar in från en laptop/webbläsare startas ingen process där. Webbläsaren styr bara backend via HTTP.",
|
||||||
@@ -722,6 +728,7 @@ const TRANSLATIONS = {
|
|||||||
"events.no_teams": "No teams created yet.",
|
"events.no_teams": "No teams created yet.",
|
||||||
"events.team_standings": "Team standings",
|
"events.team_standings": "Team standings",
|
||||||
"events.no_team_results": "No team results yet.",
|
"events.no_team_results": "No team results yet.",
|
||||||
|
"events.edit_team": "Edit team",
|
||||||
"events.add_session": "Add Session",
|
"events.add_session": "Add Session",
|
||||||
"events.set_active": "Set Active",
|
"events.set_active": "Set Active",
|
||||||
"events.assignments": "Assignments",
|
"events.assignments": "Assignments",
|
||||||
@@ -779,6 +786,7 @@ const TRANSLATIONS = {
|
|||||||
"timing.open_speaker_overlay": "Speaker overlay",
|
"timing.open_speaker_overlay": "Speaker overlay",
|
||||||
"timing.open_results_overlay": "Results overlay",
|
"timing.open_results_overlay": "Results overlay",
|
||||||
"timing.open_tv_overlay": "TV overlay",
|
"timing.open_tv_overlay": "TV overlay",
|
||||||
|
"timing.open_team_overlay": "Team overlay",
|
||||||
"timing.close_details": "Close",
|
"timing.close_details": "Close",
|
||||||
"timing.detail_title": "Leaderboard details",
|
"timing.detail_title": "Leaderboard details",
|
||||||
"timing.lap_history": "Lap history",
|
"timing.lap_history": "Lap history",
|
||||||
@@ -981,12 +989,16 @@ const TRANSLATIONS = {
|
|||||||
"overlay.mode_speaker": "Speaker",
|
"overlay.mode_speaker": "Speaker",
|
||||||
"overlay.mode_results": "Results",
|
"overlay.mode_results": "Results",
|
||||||
"overlay.mode_tv": "TV",
|
"overlay.mode_tv": "TV",
|
||||||
|
"overlay.mode_team": "Team",
|
||||||
"overlay.fastest_lap": "Fastest Lap",
|
"overlay.fastest_lap": "Fastest Lap",
|
||||||
"overlay.fullscreen": "Fullscreen",
|
"overlay.fullscreen": "Fullscreen",
|
||||||
"overlay.leaderboard_live": "Live leaderboard",
|
"overlay.leaderboard_live": "Live leaderboard",
|
||||||
"overlay.rotating_panel": "Display panel",
|
"overlay.rotating_panel": "Display panel",
|
||||||
"overlay.next_predicted_lap": "Next lap",
|
"overlay.next_predicted_lap": "Next lap",
|
||||||
"overlay.event_markers": "Event markers",
|
"overlay.event_markers": "Event markers",
|
||||||
|
"overlay.team_battle": "Team battle",
|
||||||
|
"overlay.active_member": "Active driver/car",
|
||||||
|
"overlay.top_three": "Top 3",
|
||||||
"guide.host_title": "How Managed AMMC Runs",
|
"guide.host_title": "How Managed AMMC Runs",
|
||||||
"guide.host_1": "1. AMMC always runs on the same machine where `npm start` or `node server.js` is running.",
|
"guide.host_1": "1. AMMC always runs on the same machine where `npm start` or `node server.js` is running.",
|
||||||
"guide.host_2": "2. If you only browse from a laptop/browser, no process is started there. The browser only controls the backend over HTTP.",
|
"guide.host_2": "2. If you only browse from a laptop/browser, no process is started there. The browser only controls the backend over HTTP.",
|
||||||
@@ -1024,7 +1036,7 @@ const TRANSLATIONS = {
|
|||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const overlayMode = urlParams.get("view") === "overlay";
|
const overlayMode = urlParams.get("view") === "overlay";
|
||||||
const overlayViewMode = ["leaderboard", "speaker", "results", "tv"].includes(String(urlParams.get("overlayMode") || "").toLowerCase())
|
const overlayViewMode = ["leaderboard", "speaker", "results", "tv", "team"].includes(String(urlParams.get("overlayMode") || "").toLowerCase())
|
||||||
? String(urlParams.get("overlayMode")).toLowerCase()
|
? String(urlParams.get("overlayMode")).toLowerCase()
|
||||||
: "leaderboard";
|
: "leaderboard";
|
||||||
const state = loadState();
|
const state = loadState();
|
||||||
@@ -1041,6 +1053,7 @@ let selectedDriverEditId = null;
|
|||||||
let selectedCarEditId = null;
|
let selectedCarEditId = null;
|
||||||
let selectedEventEditId = null;
|
let selectedEventEditId = null;
|
||||||
let selectedSessionEditId = null;
|
let selectedSessionEditId = null;
|
||||||
|
let selectedTeamEditId = null;
|
||||||
let quickAddDraft = null;
|
let quickAddDraft = null;
|
||||||
let overlaySyncTimer = null;
|
let overlaySyncTimer = null;
|
||||||
let overlayRotationTimer = null;
|
let overlayRotationTimer = null;
|
||||||
@@ -2749,6 +2762,10 @@ function renderEventManager(eventId) {
|
|||||||
.join("");
|
.join("");
|
||||||
const raceDrivers = event.mode === "race" ? state.drivers.filter((driver) => !event.classId || driver.classId === event.classId) : [];
|
const raceDrivers = event.mode === "race" ? state.drivers.filter((driver) => !event.classId || driver.classId === event.classId) : [];
|
||||||
const raceTeams = event.mode === "race" ? getEventTeams(event) : [];
|
const raceTeams = event.mode === "race" ? getEventTeams(event) : [];
|
||||||
|
if (selectedTeamEditId && !raceTeams.some((team) => team.id === selectedTeamEditId)) {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
}
|
||||||
|
const editingTeam = event.mode === "race" ? raceTeams.find((team) => team.id === selectedTeamEditId) || null : null;
|
||||||
const carOptions = state.cars
|
const carOptions = state.cars
|
||||||
.map((c) => `<option value="${c.id}">${escapeHtml(c.name)} (${escapeHtml(c.transponder)})</option>`)
|
.map((c) => `<option value="${c.id}">${escapeHtml(c.name)} (${escapeHtml(c.transponder)})</option>`)
|
||||||
.join("");
|
.join("");
|
||||||
@@ -2992,7 +3009,10 @@ function renderEventManager(eventId) {
|
|||||||
.join(", ") || "-"
|
.join(", ") || "-"
|
||||||
)}</div>
|
)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="actions-inline">
|
||||||
|
<button id="team-edit-${team.id}" class="btn" type="button">${t("events.edit_team")}</button>
|
||||||
<button id="team-delete-${team.id}" class="btn btn-danger" type="button">${t("common.delete")}</button>
|
<button id="team-delete-${team.id}" class="btn btn-danger" type="button">${t("common.delete")}</button>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -3163,6 +3183,59 @@ function renderEventManager(eventId) {
|
|||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${
|
||||||
|
editingTeam
|
||||||
|
? `
|
||||||
|
<div class="modal-overlay" id="teamEditModalOverlay">
|
||||||
|
<div class="modal-card">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>${t("events.edit_team")}</h3>
|
||||||
|
<button class="btn" id="teamEditCancel">${t("common.cancel")}</button>
|
||||||
|
</div>
|
||||||
|
<form id="teamEditForm" class="panel-body form-grid cols-2">
|
||||||
|
<input name="teamName" required value="${escapeHtml(editingTeam.name)}" placeholder="${t("events.team_name")}" />
|
||||||
|
<p class="form-error" id="teamEditError" hidden></p>
|
||||||
|
<div>
|
||||||
|
<h4>${t("events.team_drivers")}</h4>
|
||||||
|
<div class="check-grid">
|
||||||
|
${raceDrivers
|
||||||
|
.map(
|
||||||
|
(driver) => `
|
||||||
|
<label class="check-card">
|
||||||
|
<input type="checkbox" name="teamDriverIds" value="${driver.id}" ${editingTeam.driverIds.includes(driver.id) ? "checked" : ""} />
|
||||||
|
<span>${escapeHtml(driver.name)}${driver.transponder ? ` (${escapeHtml(driver.transponder)})` : ""}</span>
|
||||||
|
</label>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4>${t("events.team_cars")}</h4>
|
||||||
|
<div class="check-grid">
|
||||||
|
${state.cars
|
||||||
|
.map(
|
||||||
|
(car) => `
|
||||||
|
<label class="check-card">
|
||||||
|
<input type="checkbox" name="teamCarIds" value="${car.id}" ${editingTeam.carIds.includes(car.id) ? "checked" : ""} />
|
||||||
|
<span>${escapeHtml(car.name)} (${escapeHtml(car.transponder || "-")})</span>
|
||||||
|
</label>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="actions-inline">
|
||||||
|
<button class="btn btn-primary" type="submit">${t("common.save")}</button>
|
||||||
|
<button class="btn" id="teamEditCancelFooter" type="button">${t("common.cancel")}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
|
||||||
${
|
${
|
||||||
editingSession
|
editingSession
|
||||||
? `
|
? `
|
||||||
@@ -3490,13 +3563,69 @@ function renderEventManager(eventId) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
raceTeams.forEach((team) => {
|
raceTeams.forEach((team) => {
|
||||||
|
document.getElementById(`team-edit-${team.id}`)?.addEventListener("click", () => {
|
||||||
|
selectedTeamEditId = team.id;
|
||||||
|
renderEventManager(eventId);
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById(`team-delete-${team.id}`)?.addEventListener("click", () => {
|
document.getElementById(`team-delete-${team.id}`)?.addEventListener("click", () => {
|
||||||
event.raceConfig.teams = getEventTeams(event).filter((item) => item.id !== team.id);
|
event.raceConfig.teams = getEventTeams(event).filter((item) => item.id !== team.id);
|
||||||
|
if (selectedTeamEditId === team.id) {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
}
|
||||||
saveState();
|
saveState();
|
||||||
renderEventManager(eventId);
|
renderEventManager(eventId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById("teamEditCancel")?.addEventListener("click", () => {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
renderEventManager(eventId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("teamEditCancelFooter")?.addEventListener("click", () => {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
renderEventManager(eventId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("teamEditModalOverlay")?.addEventListener("click", (modalEvent) => {
|
||||||
|
if (modalEvent.target?.id === "teamEditModalOverlay") {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
renderEventManager(eventId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bindModalShell("teamEditModalOverlay", () => {
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
renderEventManager(eventId);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById("teamEditForm")?.addEventListener("submit", (submitEvent) => {
|
||||||
|
submitEvent.preventDefault();
|
||||||
|
if (!editingTeam) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const form = new FormData(submitEvent.currentTarget);
|
||||||
|
const name = String(form.get("teamName") || "").trim();
|
||||||
|
const driverIds = form.getAll("teamDriverIds").map(String).filter(Boolean);
|
||||||
|
const carIds = form.getAll("teamCarIds").map(String).filter(Boolean);
|
||||||
|
if (!name) {
|
||||||
|
setFormError("teamEditError", t("validation.required_name"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!driverIds.length && !carIds.length) {
|
||||||
|
setFormError("teamEditError", t("validation.invalid_selection"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setFormError("teamEditError", "");
|
||||||
|
event.raceConfig.teams = getEventTeams(event).map((team) =>
|
||||||
|
team.id === editingTeam.id ? normalizeRaceTeam({ ...team, name, driverIds, carIds }) : team
|
||||||
|
);
|
||||||
|
selectedTeamEditId = null;
|
||||||
|
saveState();
|
||||||
|
renderEventManager(eventId);
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById("raceFormatForm")?.addEventListener("submit", (e) => {
|
document.getElementById("raceFormatForm")?.addEventListener("submit", (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const form = new FormData(e.currentTarget);
|
const form = new FormData(e.currentTarget);
|
||||||
@@ -3764,6 +3893,7 @@ function renderTiming() {
|
|||||||
<button id="openSpeakerOverlay" class="btn" type="button">${t("timing.open_speaker_overlay")}</button>
|
<button id="openSpeakerOverlay" class="btn" type="button">${t("timing.open_speaker_overlay")}</button>
|
||||||
<button id="openResultsOverlay" class="btn" type="button">${t("timing.open_results_overlay")}</button>
|
<button id="openResultsOverlay" class="btn" type="button">${t("timing.open_results_overlay")}</button>
|
||||||
<button id="openTvOverlay" class="btn" type="button">${t("timing.open_tv_overlay")}</button>
|
<button id="openTvOverlay" class="btn" type="button">${t("timing.open_tv_overlay")}</button>
|
||||||
|
<button id="openTeamOverlay" class="btn" type="button">${t("timing.open_team_overlay")}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4004,6 +4134,7 @@ function renderTiming() {
|
|||||||
document.getElementById("openSpeakerOverlay")?.addEventListener("click", () => openOverlayWindow("speaker"));
|
document.getElementById("openSpeakerOverlay")?.addEventListener("click", () => openOverlayWindow("speaker"));
|
||||||
document.getElementById("openResultsOverlay")?.addEventListener("click", () => openOverlayWindow("results"));
|
document.getElementById("openResultsOverlay")?.addEventListener("click", () => openOverlayWindow("results"));
|
||||||
document.getElementById("openTvOverlay")?.addEventListener("click", () => openOverlayWindow("tv"));
|
document.getElementById("openTvOverlay")?.addEventListener("click", () => openOverlayWindow("tv"));
|
||||||
|
document.getElementById("openTeamOverlay")?.addEventListener("click", () => openOverlayWindow("team"));
|
||||||
document.querySelectorAll("[data-speaker-setting]").forEach((node) => {
|
document.querySelectorAll("[data-speaker-setting]").forEach((node) => {
|
||||||
node.addEventListener("change", (event) => {
|
node.addEventListener("change", (event) => {
|
||||||
const input = event.currentTarget;
|
const input = event.currentTarget;
|
||||||
@@ -4318,6 +4449,8 @@ function renderOverlay() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`
|
`
|
||||||
|
: overlayViewMode === "team"
|
||||||
|
? renderTeamOverlay(leaderboard, result, sessionTiming)
|
||||||
: overlayViewMode === "tv"
|
: overlayViewMode === "tv"
|
||||||
? `
|
? `
|
||||||
<section class="overlay-board overlay-board-tv">
|
<section class="overlay-board overlay-board-tv">
|
||||||
@@ -4592,7 +4725,10 @@ function renderLeaderboard(rows) {
|
|||||||
return `
|
return `
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="pos-pill ${posClass}">${idx + 1}</span></td>
|
<td><span class="pos-pill ${posClass}">${idx + 1}</span></td>
|
||||||
<td>${escapeHtml(row.displayName || row.driverName)}</td>
|
<td>
|
||||||
|
<div class="table-primary">${escapeHtml(row.displayName || row.driverName)}</div>
|
||||||
|
${row.teamId && row.subLabel ? `<div class="table-subnote">${t("overlay.active_member")}: ${escapeHtml(row.subLabel)}</div>` : ""}
|
||||||
|
</td>
|
||||||
<td>${escapeHtml(row.subLabel || row.carName)}</td>
|
<td>${escapeHtml(row.subLabel || row.carName)}</td>
|
||||||
<td>${escapeHtml(row.transponder)}</td>
|
<td>${escapeHtml(row.transponder)}</td>
|
||||||
<td>${row.laps}</td>
|
<td>${row.laps}</td>
|
||||||
@@ -4626,7 +4762,7 @@ function renderOverlayLeaderboard(rows) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="overlay-race-driver">
|
<div class="overlay-race-driver">
|
||||||
<strong>${escapeHtml(row.displayName || row.driverName)}</strong>
|
<strong>${escapeHtml(row.displayName || row.driverName)}</strong>
|
||||||
<span>${escapeHtml(row.subLabel || row.transponder || "-")}</span>
|
<span>${escapeHtml(row.teamId && row.subLabel ? `${t("overlay.active_member")}: ${row.subLabel}` : row.subLabel || row.transponder || "-")}</span>
|
||||||
<div class="overlay-prediction">
|
<div class="overlay-prediction">
|
||||||
<div class="overlay-prediction-meta">
|
<div class="overlay-prediction-meta">
|
||||||
<label>${t("overlay.next_predicted_lap")}</label>
|
<label>${t("overlay.next_predicted_lap")}</label>
|
||||||
@@ -4661,6 +4797,63 @@ function renderOverlayLeaderboard(rows) {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderTeamOverlay(rows, result, sessionTiming) {
|
||||||
|
const topThree = rows.slice(0, 3);
|
||||||
|
return `
|
||||||
|
<section class="overlay-team-layout">
|
||||||
|
<section class="overlay-team-podium">
|
||||||
|
<div class="overlay-section-head">
|
||||||
|
<h3>${t("overlay.top_three")}</h3>
|
||||||
|
<span class="pill">${t("overlay.team_battle")}</span>
|
||||||
|
</div>
|
||||||
|
<div class="overlay-team-podium-grid">
|
||||||
|
${topThree
|
||||||
|
.map(
|
||||||
|
(row, index) => `
|
||||||
|
<article class="overlay-team-card overlay-team-card-${index + 1}">
|
||||||
|
<span class="pos-pill pos-${Math.min(index + 1, 3)}">${index + 1}</span>
|
||||||
|
<strong>${escapeHtml(row.displayName || row.driverName)}</strong>
|
||||||
|
<p>${escapeHtml(row.resultDisplay || "-")}</p>
|
||||||
|
<small>${t("overlay.active_member")}: ${escapeHtml(row.subLabel || "-")}</small>
|
||||||
|
</article>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="overlay-board overlay-board-tv">
|
||||||
|
<div class="overlay-table-wrap overlay-display-wrap">
|
||||||
|
<section class="overlay-stats-row">
|
||||||
|
<article class="overlay-stat-card">
|
||||||
|
<span>${t("table.laps")}</span>
|
||||||
|
<strong>${rows[0]?.laps || 0}</strong>
|
||||||
|
<small>${escapeHtml(rows[0]?.displayName || rows[0]?.driverName || "-")}</small>
|
||||||
|
</article>
|
||||||
|
<article class="overlay-stat-card">
|
||||||
|
<span>${t("timing.total_passings")}</span>
|
||||||
|
<strong>${result?.passings.length || 0}</strong>
|
||||||
|
<small>${sessionTiming?.untimed ? t("timing.elapsed") : t("timing.remaining")}</small>
|
||||||
|
</article>
|
||||||
|
<article class="overlay-stat-card">
|
||||||
|
<span>${t("overlay.fastest_lap")}</span>
|
||||||
|
<strong>${formatLap([...rows].filter((row) => Number.isFinite(row.bestLapMs)).sort((a, b) => a.bestLapMs - b.bestLapMs)[0]?.bestLapMs)}</strong>
|
||||||
|
<small>${escapeHtml(
|
||||||
|
[...rows].filter((row) => Number.isFinite(row.bestLapMs)).sort((a, b) => a.bestLapMs - b.bestLapMs)[0]?.displayName || "-"
|
||||||
|
)}</small>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
|
<div class="overlay-leaderboard-card overlay-leaderboard-card-tv">
|
||||||
|
<div class="overlay-section-head">
|
||||||
|
<h3>${t("events.team_standings")}</h3>
|
||||||
|
</div>
|
||||||
|
${renderOverlayLeaderboard(rows)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function renderRecentPassings(session) {
|
function renderRecentPassings(session) {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return `<p>${t("timing.no_session_selected")}</p>`;
|
return `<p>${t("timing.no_session_selected")}</p>`;
|
||||||
@@ -5913,7 +6106,10 @@ function renderTeamRaceStandings(event) {
|
|||||||
(row, index) => `
|
(row, index) => `
|
||||||
<tr>
|
<tr>
|
||||||
<td>${index + 1}</td>
|
<td>${index + 1}</td>
|
||||||
<td>${escapeHtml(row.displayName || row.driverName)}</td>
|
<td>
|
||||||
|
<div class="table-primary">${escapeHtml(row.displayName || row.driverName)}</div>
|
||||||
|
${row.subLabel ? `<div class="table-subnote">${t("overlay.active_member")}: ${escapeHtml(row.subLabel)}</div>` : ""}
|
||||||
|
</td>
|
||||||
<td>${row.laps}</td>
|
<td>${row.laps}</td>
|
||||||
<td>${escapeHtml(row.resultDisplay)}</td>
|
<td>${escapeHtml(row.resultDisplay)}</td>
|
||||||
<td>${formatLap(row.bestLapMs)}</td>
|
<td>${formatLap(row.bestLapMs)}</td>
|
||||||
|
|||||||
@@ -609,6 +609,16 @@ select:focus {
|
|||||||
margin: 0 0 10px;
|
margin: 0 0 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table-primary {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-subnote {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.grid-editor-toolbar {
|
.grid-editor-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -881,6 +891,49 @@ select:focus {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay-team-layout {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-podium {
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 18px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(7, 12, 20, 0.82);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-podium-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-card {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-card strong {
|
||||||
|
font-family: Orbitron, sans-serif;
|
||||||
|
font-size: clamp(1.2rem, 2vw, 1.9rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-card p,
|
||||||
|
.overlay-team-card small {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-team-card-1 {
|
||||||
|
border-color: rgba(225, 6, 0, 0.45);
|
||||||
|
background: linear-gradient(135deg, rgba(225, 6, 0, 0.12), rgba(255, 255, 255, 0.03));
|
||||||
|
}
|
||||||
|
|
||||||
.overlay-table-wrap,
|
.overlay-table-wrap,
|
||||||
.overlay-side-card,
|
.overlay-side-card,
|
||||||
.overlay-empty {
|
.overlay-empty {
|
||||||
@@ -1268,6 +1321,10 @@ select:focus {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay-team-podium-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.overlay-header {
|
.overlay-header {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user