Add scene backdrop controls, FPS display, and toned backdrops

This commit is contained in:
Daddy32
2025-12-16 21:33:01 +01:00
parent 9d554c8805
commit 7a025ce68d
6 changed files with 245 additions and 7 deletions

View File

@@ -64,6 +64,14 @@
glowStrength: 0.55,
gradientBlend: true,
},
backdrop: {
enabled: true,
colors: null, // optional array; defaults derived from palette
opacity: 0.19,
blur: 24,
speedSec: 30,
},
showFps: true,
};
return {

View File

@@ -12,6 +12,7 @@
getMaxLinkDistance,
updateHud,
checkWinCondition,
ui,
}) => {
const runSceneBeforeUpdateHook = () => {
const currentScene = getCurrentScene();
@@ -297,6 +298,7 @@
Events.on(engine, "afterUpdate", keepBallsInBounds);
Events.on(engine, "beforeUpdate", beforeUpdateStep);
Events.on(render, "afterRender", drawLinkGlowAndSparkles);
Events.on(render, "afterRender", () => ui.updateFps && ui.updateFps());
};
window.PhysilinksLoop = { create };

View File

@@ -73,8 +73,27 @@
...goalEffects,
});
const normalizeBackdrop = (
backdrop = {},
defaults = baseConfig.backdrop || {},
) => ({
...defaults,
...backdrop,
colors: Array.isArray(backdrop.colors)
? [...backdrop.colors]
: Array.isArray(defaults.colors)
? [...defaults.colors]
: null,
});
const normalizeSceneConfig = (sceneConfig = {}, defaults = baseConfig) => {
const { link = {}, messages = {}, goalEffects = {}, ...rest } = sceneConfig;
const {
link = {},
messages = {},
goalEffects = {},
backdrop = {},
...rest
} = sceneConfig;
const base = defaults || {};
return {
...base,
@@ -85,6 +104,7 @@
base.messages || defaultMessageConfig,
),
goalEffects: normalizeGoalEffects(goalEffects, base.goalEffects),
backdrop: normalizeBackdrop(backdrop, base.backdrop),
};
};
@@ -216,6 +236,8 @@
setSceneIdInUrl(next.id);
const prevRadius = config.ballRadius;
setConfigForScene(next.config);
ui.setBackdrop(config.backdrop, config.palette);
ui.setFpsVisibility(config.showFps);
ui.setMessageDefaults(config.messages);
resetEngineForScene(next.config, { prevRadius });
state.clearedCount = 0;
@@ -525,6 +547,7 @@
getMaxLinkDistance,
updateHud,
checkWinCondition,
ui,
});
window.addEventListener("resize", handleResize);

View File

@@ -149,6 +149,14 @@
glowStrength: 0.55, // opacity multiplier for glow
gradientBlend: true, // when colors are provided, blend them into the bar
},
backdrop: {
enabled: true, // disable to turn off scene backdrop
colors: null, // array of colors for backdrop blobs; defaults derive from palette
opacity: 0.24, // base opacity
blur: 24, // blur radius in px
speedSec: 30, // drift speed in seconds
},
showFps: false, // set true to show FPS overlay for perf checking
},
createBodies: (w, h) => {
// Return an array of Matter bodies that make up scene obstacles/boundaries.

107
src/ui.js
View File

@@ -423,6 +423,110 @@
}
};
const hexToRgb = (hex) => {
if (typeof hex !== "string") return null;
const match = hex
.trim()
.toLowerCase()
.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
if (!match) return null;
let value = match[1];
if (value.length === 3) {
value = value
.split("")
.map((c) => c + c)
.join("");
}
const num = parseInt(value, 16);
return {
r: (num >> 16) & 255,
g: (num >> 8) & 255,
b: num & 255,
};
};
const deriveOppositeColors = (palette = []) => {
const alphas = [0.4, 0.32, 0.24];
return palette
.slice(0, 3)
.map((c, idx) => {
const rgb = hexToRgb(c);
if (!rgb) return null;
const opp = {
r: 255 - rgb.r,
g: 255 - rgb.g,
b: 255 - rgb.b,
};
return `rgba(${opp.r}, ${opp.g}, ${opp.b}, ${alphas[idx] ?? 0.1})`;
})
.filter(Boolean);
};
const setBackdrop = (backdrop = {}, palette = []) => {
if (!sceneEl) return;
const derived =
Array.isArray(backdrop.colors) && backdrop.colors.length > 0
? backdrop.colors
: deriveOppositeColors(palette);
const colors =
derived && derived.length > 0
? derived
: [
"rgba(56, 189, 248, 0.08)",
"rgba(167, 139, 250, 0.07)",
"rgba(52, 211, 153, 0.06)",
];
if (backdrop.enabled === false) {
sceneEl.style.removeProperty("--backdrop-opacity");
sceneEl.style.removeProperty("--backdrop-blur");
sceneEl.style.removeProperty("--backdrop-speed");
sceneEl.style.removeProperty("--backdrop-color-a");
sceneEl.style.removeProperty("--backdrop-color-b");
sceneEl.style.removeProperty("--backdrop-color-c");
sceneEl.classList.remove("backdrop-enabled");
return;
}
const [
c1 = "rgba(56, 189, 248, 0.16)",
c2 = "rgba(167, 139, 250, 0.14)",
c3 = "rgba(52, 211, 153, 0.12)",
] = colors;
sceneEl.style.setProperty(
"--backdrop-opacity",
`${backdrop.opacity ?? 0.24}`,
);
sceneEl.style.setProperty("--backdrop-blur", `${backdrop.blur ?? 24}px`);
const speed = Math.max(8, backdrop.speedSec ?? 30);
sceneEl.style.setProperty("--backdrop-speed", `${speed}s`);
sceneEl.style.setProperty("--backdrop-color-a", c1);
sceneEl.style.setProperty("--backdrop-color-b", c2);
sceneEl.style.setProperty("--backdrop-color-c", c3);
sceneEl.classList.add("backdrop-enabled");
};
const fpsEl = document.createElement("div");
fpsEl.className = "fps-counter";
fpsEl.textContent = "FPS: —";
if (sceneEl) sceneEl.appendChild(fpsEl);
let fpsVisible = false;
const setFpsVisibility = (on) => {
fpsVisible = !!on;
fpsEl.classList.toggle("visible", fpsVisible);
};
let lastFpsUpdate = 0;
let frameCount = 0;
const updateFps = () => {
if (!fpsVisible) return;
frameCount += 1;
const now = performance.now();
if (now - lastFpsUpdate >= 500) {
const fps = Math.round((frameCount * 1000) / (now - lastFpsUpdate));
fpsEl.textContent = `FPS: ${fps}`;
frameCount = 0;
lastFpsUpdate = now;
}
};
const api = {
sceneEl,
updateHud,
@@ -441,6 +545,9 @@
setMessageDefaults,
clearMessages,
spawnClearEffects,
setBackdrop,
setFpsVisibility,
updateFps,
};
return api;
};