Harden CSRF/CSP and add hash version upgrades

This commit is contained in:
2026-02-18 20:51:18 +01:00
parent 3c7f3d2114
commit a130cba41a
23 changed files with 627 additions and 57 deletions

47
wwwroot/js/effects.js vendored
View File

@@ -3,48 +3,15 @@
// Screenshot hover ---------------------------------------------------
export function setupCardVisualHover(el, url) {
if (!el || !url) return;
const img = new Image();
let naturalW = 0;
let naturalH = 0;
let loaded = false;
img.src = url;
img.onload = () => {
naturalW = img.naturalWidth;
naturalH = img.naturalHeight;
loaded = true;
};
const reset = () => {
el.classList.remove("hovering");
el.style.backgroundSize = "";
el.style.backgroundPosition = "";
el.style.backgroundRepeat = "";
};
el.addEventListener("mouseenter", () => {
el.classList.add("hovering");
el.style.backgroundSize = "auto";
el.style.backgroundRepeat = "no-repeat";
el.style.backgroundPosition = "center";
});
el.addEventListener("mousemove", (e) => {
if (!loaded) return;
const rect = el.getBoundingClientRect();
const overW = naturalW - rect.width;
const overH = naturalH - rect.height;
if (overW <= 0 && overH <= 0) {
el.style.backgroundPosition = "center";
return;
}
const xRatio = (e.clientX - rect.left) / rect.width;
const yRatio = (e.clientY - rect.top) / rect.height;
const xPercent = overW > 0 ? xRatio * 100 : 50;
const yPercent = overH > 0 ? yRatio * 100 : 50;
el.style.backgroundPosition = `${xPercent}% ${yPercent}%`;
});
["mouseleave", "blur"].forEach((evt) => el.addEventListener(evt, reset));
["mouseleave", "blur"].forEach((evt) =>
el.addEventListener(evt, () => {
el.classList.remove("hovering");
}),
);
}
// Celebration FX -----------------------------------------------------
@@ -57,10 +24,6 @@ function ensureFxCanvas() {
if (fxCanvas) return;
fxCanvas = document.createElement("canvas");
fxCanvas.className = "fx-canvas";
fxCanvas.style.position = "fixed";
fxCanvas.style.inset = "0";
fxCanvas.style.pointerEvents = "none";
fxCanvas.style.zIndex = "120";
fxCanvas.width = window.innerWidth;
fxCanvas.height = window.innerHeight;
fxCtx = fxCanvas.getContext("2d");

View File

@@ -6,7 +6,6 @@ import { setupCardVisualHover, triggerCelebration } from "./effects.js";
import { renderAdminLinker } from "./admin-ui.js";
import { getUiRuntime } from "./ui-runtime.js";
import {
cssEscapeUrl,
escapeHtml,
isLinked,
linkedPeerTitles,
@@ -95,7 +94,7 @@ export function buildCard(
: "";
const visual =
hasImage && safeShot
? `<button class="card-visual" data-img="${safeShot}" aria-label="${t("card.openScreenshot")}" style="background-image:url('${cssEscapeUrl(safeShot)}')"></button>`
? `<button class="card-visual has-image" data-img="${escapeHtml(safeShot)}" aria-label="${t("card.openScreenshot")}"><img class="card-visual-image" src="${escapeHtml(safeShot)}" alt="" loading="lazy" decoding="async" /></button>`
: `<div class="card-visual"></div>`;
const hasPlayers = s.minPlayers || s.maxPlayers;
const players = hasPlayers