Add pause menu concept prototype
Adds a standalone Commonwealth Online pause menu mockup with the Fallout-style map, left-side navigation, server info, controller prompts, and keyboard handling. Also includes map zoom/pan behavior, fixes the UI scale custom property so sizes resolve correctly, and updates the changelog and dev log.
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// ── Compute UI scale (fixes browser incompatibility with nested calc/min/clamp in custom props) ──
|
||||
// Some browsers don't resolve nested calc()/min()/clamp() inside custom properties to a number,
|
||||
// leaving them as formula strings. That breaks calc(Npx * var(--co-ui-scale)) throughout the sheet.
|
||||
// Instead, we compute the scale in JS and set it as a numeric value, which works everywhere.
|
||||
function updateScale() {
|
||||
var MIN_SCALE = 0.5;
|
||||
var MAX_SCALE = 1.6;
|
||||
var BASELINE_WIDTH = 1920;
|
||||
var BASELINE_HEIGHT = 1080;
|
||||
var scale = Math.min(
|
||||
window.innerWidth / BASELINE_WIDTH,
|
||||
window.innerHeight / BASELINE_HEIGHT
|
||||
);
|
||||
scale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, scale));
|
||||
document.documentElement.style.setProperty("--co-ui-scale", String(scale));
|
||||
}
|
||||
|
||||
updateScale();
|
||||
window.addEventListener("resize", updateScale);
|
||||
|
||||
var menu = document.getElementById("menu");
|
||||
if (!menu) {
|
||||
return;
|
||||
}
|
||||
|
||||
var items = Array.prototype.slice.call(menu.querySelectorAll(".menu-item"));
|
||||
var selectedIndex = 1; // MAP is default
|
||||
|
||||
function setSelected(index) {
|
||||
if (index < 0 || index >= items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = index;
|
||||
|
||||
items.forEach(function (item, i) {
|
||||
var isSelected = i === selectedIndex;
|
||||
item.classList.toggle("is-selected", isSelected);
|
||||
item.setAttribute("aria-selected", isSelected ? "true" : "false");
|
||||
item.tabIndex = isSelected ? 0 : -1;
|
||||
});
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
var item = items[selectedIndex];
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
var action = item.getAttribute("data-action");
|
||||
console.log("[CO Pause Menu] Selected:", action);
|
||||
|
||||
if (action === "resume") {
|
||||
console.log("[CO Pause Menu] Resume game (concept)");
|
||||
} else if (action === "quit") {
|
||||
console.log("[CO Pause Menu] Quit to main menu (concept)");
|
||||
}
|
||||
}
|
||||
|
||||
function moveSelection(delta) {
|
||||
var next = selectedIndex + delta;
|
||||
if (next < 0) {
|
||||
next = items.length - 1;
|
||||
} else if (next >= items.length) {
|
||||
next = 0;
|
||||
}
|
||||
setSelected(next);
|
||||
}
|
||||
|
||||
items.forEach(function (item, index) {
|
||||
item.addEventListener("click", function () {
|
||||
setSelected(index);
|
||||
activateSelected();
|
||||
});
|
||||
|
||||
item.addEventListener("mouseenter", function () {
|
||||
setSelected(index);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
switch (event.key) {
|
||||
case "ArrowUp":
|
||||
event.preventDefault();
|
||||
moveSelection(-1);
|
||||
break;
|
||||
case "ArrowDown":
|
||||
event.preventDefault();
|
||||
moveSelection(1);
|
||||
break;
|
||||
case "Enter":
|
||||
case " ":
|
||||
event.preventDefault();
|
||||
activateSelected();
|
||||
break;
|
||||
case "Escape":
|
||||
event.preventDefault();
|
||||
console.log("[CO Pause Menu] Back / close pause menu (concept)");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
setSelected(selectedIndex);
|
||||
|
||||
// ── Map pan/zoom (concept preview of future Fallout 76-style navigation) ──
|
||||
// Scroll to zoom, click-drag to pan once zoomed in. Real implementation
|
||||
// will eventually read controller right-stick input via F4SE instead.
|
||||
var pauseScreen = document.getElementById("pauseScreen");
|
||||
var mapLayer = document.querySelector(".map-layer");
|
||||
|
||||
if (pauseScreen && mapLayer) {
|
||||
var MIN_ZOOM = 1;
|
||||
var MAX_ZOOM = 4;
|
||||
var ZOOM_STEP = 0.18;
|
||||
var zoom = MIN_ZOOM;
|
||||
var panX = 0;
|
||||
var panY = 0;
|
||||
var isDragging = false;
|
||||
var lastX = 0;
|
||||
var lastY = 0;
|
||||
|
||||
function applyMapTransform() {
|
||||
pauseScreen.style.setProperty("--map-zoom", String(zoom));
|
||||
pauseScreen.style.setProperty("--map-pan-x", panX + "px");
|
||||
pauseScreen.style.setProperty("--map-pan-y", panY + "px");
|
||||
}
|
||||
|
||||
function clampPan() {
|
||||
var maxOffsetX = ((zoom - 1) * window.innerWidth) / 2;
|
||||
var maxOffsetY = ((zoom - 1) * window.innerHeight) / 2;
|
||||
panX = Math.max(-maxOffsetX, Math.min(maxOffsetX, panX));
|
||||
panY = Math.max(-maxOffsetY, Math.min(maxOffsetY, panY));
|
||||
}
|
||||
|
||||
mapLayer.addEventListener(
|
||||
"wheel",
|
||||
function (event) {
|
||||
event.preventDefault();
|
||||
var delta = event.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
|
||||
zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom + delta));
|
||||
if (zoom === MIN_ZOOM) {
|
||||
panX = 0;
|
||||
panY = 0;
|
||||
} else {
|
||||
clampPan();
|
||||
}
|
||||
applyMapTransform();
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
|
||||
mapLayer.addEventListener("mousedown", function (event) {
|
||||
if (zoom <= MIN_ZOOM) {
|
||||
return;
|
||||
}
|
||||
isDragging = true;
|
||||
lastX = event.clientX;
|
||||
lastY = event.clientY;
|
||||
pauseScreen.classList.add("is-panning");
|
||||
});
|
||||
|
||||
window.addEventListener("mousemove", function (event) {
|
||||
if (!isDragging) {
|
||||
return;
|
||||
}
|
||||
panX += event.clientX - lastX;
|
||||
panY += event.clientY - lastY;
|
||||
lastX = event.clientX;
|
||||
lastY = event.clientY;
|
||||
clampPan();
|
||||
applyMapTransform();
|
||||
});
|
||||
|
||||
window.addEventListener("mouseup", function () {
|
||||
isDragging = false;
|
||||
pauseScreen.classList.remove("is-panning");
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user