Consolidate shared UI assets
Move common logo and controller glyphs into `ui/views/CommonwealthOnline/shared/` and update the pause menu, main menu, and browser glyph loader to use the shared paths. Also add a sync script and update deploy/staging scripts to copy controller icons once into the shared folder.
This commit is contained in:
@@ -21,7 +21,7 @@
|
||||
|
||||
var PLATFORMS = {
|
||||
Xbox: {
|
||||
base: "./assets/icons/Xbox/",
|
||||
base: "../shared/icons/Xbox/",
|
||||
files: {
|
||||
a: "T_X_A_White_Alt.png",
|
||||
b: "T_X_B_White_Alt.png",
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
Playstation: {
|
||||
base: "./assets/icons/Playstation/",
|
||||
base: "../shared/icons/Playstation/",
|
||||
files: {
|
||||
a: "T_P5_Cross_Alt.png",
|
||||
b: "T_P5_Circle_Alt.png",
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
},
|
||||
Switch: {
|
||||
base: "./assets/icons/Switch/",
|
||||
base: "../shared/icons/Switch/",
|
||||
files: {
|
||||
a: "T_S_A_Alt.png",
|
||||
b: "T_S_B_Alt.png",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Commonwealth Online — Main Menu</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="../settings/settings.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-screen" id="mainScreen">
|
||||
<!-- Title-screen scenic background -->
|
||||
<div class="scene-layer" aria-hidden="true"></div>
|
||||
<div class="scene-overlay" aria-hidden="true"></div>
|
||||
|
||||
<!-- CRT / HUD texture overlays -->
|
||||
<div class="screen-effects" aria-hidden="true"></div>
|
||||
|
||||
<!-- Left main menu panel -->
|
||||
<aside class="left-panel">
|
||||
<img
|
||||
class="brand-logo"
|
||||
src="../shared/logo.svg"
|
||||
alt="Commonwealth Online"
|
||||
width="220"
|
||||
height="54"
|
||||
>
|
||||
|
||||
<nav class="menu" id="menu" aria-label="Main menu">
|
||||
<button type="button" class="menu-item" data-index="0" data-action="multiplayer">
|
||||
<span class="menu-item__label">MULTIPLAYER</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="menu-item" data-index="1" data-action="creations">
|
||||
<span class="menu-item__label">CREATIONS</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="menu-item" data-index="2" data-action="settings">
|
||||
<span class="menu-item__label">SETTINGS</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="menu-item" data-index="3" data-action="help">
|
||||
<span class="menu-item__label">HELP</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<button type="button" class="menu-item" data-index="4" data-action="quit">
|
||||
<span class="menu-item__label">QUIT</span>
|
||||
<span class="menu-item__bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Settings view (content mounted by ../settings/settings.js) -->
|
||||
<section class="settings-view" id="settingsPanel" aria-label="Settings" hidden></section>
|
||||
|
||||
<!-- Bottom controller prompts (main menu) -->
|
||||
<footer class="prompt-bar" id="mainPromptBar" aria-label="Controller prompts">
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_A_White_Alt.png" src="../shared/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">SELECT</span>
|
||||
</div>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_B_White_Alt.png" src="../shared/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">BACK</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Settings mode prompt bar -->
|
||||
<footer class="prompt-bar prompt-bar--settings" id="settingsPromptBar" aria-label="Settings prompts" hidden>
|
||||
<div class="prompt" data-prompt="move">
|
||||
<span class="prompt__glyph prompt__glyph--text" aria-hidden="true">↑↓</span>
|
||||
<span class="prompt__label">MOVE</span>
|
||||
</div>
|
||||
<div class="prompt" data-prompt="change">
|
||||
<span class="prompt__glyph prompt__glyph--text" aria-hidden="true">←→</span>
|
||||
<span class="prompt__label">CHANGE</span>
|
||||
</div>
|
||||
<div class="prompt" data-prompt="select">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_A_White_Alt.png" src="../shared/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">SELECT</span>
|
||||
</div>
|
||||
<div class="prompt" data-prompt="back">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_B_White_Alt.png" src="../shared/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">BACK</span>
|
||||
</div>
|
||||
<div class="prompt" data-prompt="tab">
|
||||
<span class="prompt__glyph prompt__glyph--text" aria-hidden="true">TAB</span>
|
||||
<span class="prompt__label">SWITCH FOCUS</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="../settings/settings-data.js"></script>
|
||||
<script src="../settings/settings.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,399 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
// ── C++ / F4SE bridge placeholders ─────────────────────────────
|
||||
window.CommonwealthOnlineUI = {
|
||||
openGameSettings: function () {
|
||||
console.log("[CO UI] Request open vanilla Fallout 4 SettingsMenu");
|
||||
},
|
||||
setMultiplayerSetting: function (key, value) {
|
||||
console.log("[CO UI] Multiplayer setting changed:", key, value);
|
||||
},
|
||||
onBack: function () {
|
||||
console.log("[CO UI] Back requested from main menu");
|
||||
},
|
||||
onOpenView: function (viewName) {
|
||||
console.log("[CO UI] Open view:", viewName);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Compute UI scale ───────────────────────────────────────────
|
||||
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);
|
||||
|
||||
// ── Tint Xbox prompt glyphs to menu amber ──────────────────────
|
||||
var GLYPH_WHITE_THRESHOLD = 200;
|
||||
var GLYPH_BLACK_THRESHOLD = 40;
|
||||
var glyphTintCache = {};
|
||||
|
||||
function getAmberTint() {
|
||||
var style = getComputedStyle(document.documentElement);
|
||||
var amber = style.getPropertyValue("--co-amber").trim() || "#c9a84a";
|
||||
var hex = amber.replace("#", "");
|
||||
if (hex.length === 3) {
|
||||
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
||||
}
|
||||
return {
|
||||
r: parseInt(hex.slice(0, 2), 16) || 201,
|
||||
g: parseInt(hex.slice(2, 4), 16) || 168,
|
||||
b: parseInt(hex.slice(4, 6), 16) || 74
|
||||
};
|
||||
}
|
||||
|
||||
function classifyGlyphPixel(r, g, b) {
|
||||
if (r >= GLYPH_WHITE_THRESHOLD && g >= GLYPH_WHITE_THRESHOLD && b >= GLYPH_WHITE_THRESHOLD) {
|
||||
return "white";
|
||||
}
|
||||
if (r <= GLYPH_BLACK_THRESHOLD && g <= GLYPH_BLACK_THRESHOLD && b <= GLYPH_BLACK_THRESHOLD) {
|
||||
return "black";
|
||||
}
|
||||
return "edge";
|
||||
}
|
||||
|
||||
function tintGlyphImageData(imageData, color) {
|
||||
var data = imageData.data;
|
||||
for (var i = 0; i < data.length; i += 4) {
|
||||
if (data[i + 3] < 4) {
|
||||
data[i + 3] = 0;
|
||||
continue;
|
||||
}
|
||||
var kind = classifyGlyphPixel(data[i], data[i + 1], data[i + 2]);
|
||||
if (kind === "white") {
|
||||
data[i] = color.r;
|
||||
data[i + 1] = color.g;
|
||||
data[i + 2] = color.b;
|
||||
} else if (kind === "black") {
|
||||
data[i] = 0;
|
||||
data[i + 1] = 0;
|
||||
data[i + 2] = 0;
|
||||
} else {
|
||||
var lum = (data[i] + data[i + 1] + data[i + 2]) / (3 * 255);
|
||||
data[i] = Math.round(lum * color.r);
|
||||
data[i + 1] = Math.round(lum * color.g);
|
||||
data[i + 2] = Math.round(lum * color.b);
|
||||
}
|
||||
}
|
||||
return imageData;
|
||||
}
|
||||
|
||||
function tintGlyphImage(url, color, done) {
|
||||
var key = url + "|" + color.r + "," + color.g + "," + color.b;
|
||||
if (glyphTintCache[key]) {
|
||||
done(glyphTintCache[key]);
|
||||
return;
|
||||
}
|
||||
var img = new Image();
|
||||
img.onload = function () {
|
||||
var canvas = document.createElement("canvas");
|
||||
canvas.width = img.naturalWidth;
|
||||
canvas.height = img.naturalHeight;
|
||||
var ctx = canvas.getContext("2d");
|
||||
if (!ctx) {
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0);
|
||||
try {
|
||||
ctx.putImageData(
|
||||
tintGlyphImageData(ctx.getImageData(0, 0, canvas.width, canvas.height), color),
|
||||
0,
|
||||
0
|
||||
);
|
||||
} catch (err) {
|
||||
done(null);
|
||||
return;
|
||||
}
|
||||
var dataUrl = canvas.toDataURL("image/png");
|
||||
glyphTintCache[key] = dataUrl;
|
||||
done(dataUrl);
|
||||
};
|
||||
img.onerror = function () {
|
||||
done(null);
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
function hydratePromptGlyphs() {
|
||||
var color = getAmberTint();
|
||||
var imgs = document.querySelectorAll(".prompt__glyph-img[data-glyph-src]");
|
||||
for (var i = 0; i < imgs.length; i++) {
|
||||
(function (img) {
|
||||
var src = img.getAttribute("data-glyph-src");
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
tintGlyphImage(src, color, function (dataUrl) {
|
||||
if (dataUrl) {
|
||||
img.src = dataUrl;
|
||||
}
|
||||
});
|
||||
})(imgs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
hydratePromptGlyphs();
|
||||
|
||||
// ── DOM refs ───────────────────────────────────────────────────
|
||||
|
||||
var mainScreen = document.getElementById("mainScreen");
|
||||
var menu = document.getElementById("menu");
|
||||
var settingsPanel = document.getElementById("settingsPanel");
|
||||
var mainPromptBar = document.getElementById("mainPromptBar");
|
||||
var settingsPromptBar = document.getElementById("settingsPromptBar");
|
||||
|
||||
if (!menu || !settingsPanel || !window.CO_Settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
var settingsController = null;
|
||||
|
||||
var items = Array.prototype.slice.call(menu.querySelectorAll(".menu-item"));
|
||||
var SETTINGS_INDEX = 2;
|
||||
|
||||
// viewMode: "menu" | "settings"
|
||||
var viewMode = "menu";
|
||||
var selectedIndex = -1;
|
||||
var SETTINGS_TRANSITION_MS = 220;
|
||||
var settingsHideFallbackId = 0;
|
||||
var settingsHideOnTransitionEnd = null;
|
||||
|
||||
// ── Main menu selection ────────────────────────────────────────
|
||||
|
||||
function setSelected(index) {
|
||||
if (index >= items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIndex = index < 0 ? -1 : 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 moveSelection(delta) {
|
||||
var next = selectedIndex + delta;
|
||||
if (selectedIndex < 0) {
|
||||
next = delta > 0 ? 0 : items.length - 1;
|
||||
} else if (next < 0) {
|
||||
next = items.length - 1;
|
||||
} else if (next >= items.length) {
|
||||
next = 0;
|
||||
}
|
||||
|
||||
// Skip disabled items
|
||||
var guard = 0;
|
||||
while (items[next] && items[next].classList.contains("is-disabled") && guard < items.length) {
|
||||
next += delta > 0 ? 1 : -1;
|
||||
if (next < 0) {
|
||||
next = items.length - 1;
|
||||
} else if (next >= items.length) {
|
||||
next = 0;
|
||||
}
|
||||
guard += 1;
|
||||
}
|
||||
|
||||
setSelected(next);
|
||||
}
|
||||
|
||||
function activateSelected() {
|
||||
var item = items[selectedIndex];
|
||||
if (!item || item.classList.contains("is-disabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var action = item.getAttribute("data-action");
|
||||
console.log("[CO Main Menu] Selected:", action);
|
||||
|
||||
if (action === "settings") {
|
||||
enterSettingsMode();
|
||||
} else {
|
||||
window.CommonwealthOnlineUI.onOpenView(action);
|
||||
if (action !== "quit") {
|
||||
setSelected(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.forEach(function (item, index) {
|
||||
item.addEventListener("click", function () {
|
||||
if (viewMode === "settings") {
|
||||
if (index === SETTINGS_INDEX) {
|
||||
return;
|
||||
}
|
||||
exitSettingsMode();
|
||||
}
|
||||
if (item.classList.contains("is-disabled")) {
|
||||
return;
|
||||
}
|
||||
setSelected(index);
|
||||
activateSelected();
|
||||
});
|
||||
});
|
||||
|
||||
// ── View mode transitions ──────────────────────────────────────
|
||||
|
||||
function cancelSettingsPanelHide() {
|
||||
if (settingsHideFallbackId) {
|
||||
window.clearTimeout(settingsHideFallbackId);
|
||||
settingsHideFallbackId = 0;
|
||||
}
|
||||
if (settingsHideOnTransitionEnd) {
|
||||
settingsPanel.removeEventListener("transitionend", settingsHideOnTransitionEnd);
|
||||
settingsHideOnTransitionEnd = null;
|
||||
}
|
||||
}
|
||||
|
||||
function showSettingsPanelAnimated() {
|
||||
cancelSettingsPanelHide();
|
||||
if (mainScreen) {
|
||||
mainScreen.classList.add("is-settings-mode");
|
||||
}
|
||||
settingsPanel.hidden = false;
|
||||
settingsPanel.classList.remove("is-open");
|
||||
void settingsPanel.offsetWidth;
|
||||
settingsPanel.classList.add("is-open");
|
||||
}
|
||||
|
||||
function hideSettingsPanelAnimated(done) {
|
||||
if (settingsPanel.hidden) {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
cancelSettingsPanelHide();
|
||||
settingsPanel.classList.remove("is-open");
|
||||
|
||||
var completed = false;
|
||||
|
||||
function finishHide() {
|
||||
if (completed) {
|
||||
return;
|
||||
}
|
||||
completed = true;
|
||||
cancelSettingsPanelHide();
|
||||
if (viewMode === "settings" || settingsPanel.classList.contains("is-open")) {
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mainScreen) {
|
||||
mainScreen.classList.remove("is-settings-mode");
|
||||
}
|
||||
settingsPanel.hidden = true;
|
||||
if (done) {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
settingsHideOnTransitionEnd = function (event) {
|
||||
if (event.target !== settingsPanel || event.propertyName !== "opacity") {
|
||||
return;
|
||||
}
|
||||
finishHide();
|
||||
};
|
||||
|
||||
settingsPanel.addEventListener("transitionend", settingsHideOnTransitionEnd);
|
||||
settingsHideFallbackId = window.setTimeout(finishHide, SETTINGS_TRANSITION_MS + 80);
|
||||
}
|
||||
|
||||
function enterSettingsMode() {
|
||||
viewMode = "settings";
|
||||
|
||||
console.log("[CO Main Menu] Entering settings mode");
|
||||
window.CommonwealthOnlineUI.onOpenView("settings");
|
||||
|
||||
setSelected(SETTINGS_INDEX);
|
||||
|
||||
showSettingsPanelAnimated();
|
||||
if (mainPromptBar) {
|
||||
mainPromptBar.hidden = true;
|
||||
}
|
||||
|
||||
settingsController.show();
|
||||
}
|
||||
|
||||
function exitSettingsMode() {
|
||||
if (viewMode !== "settings") {
|
||||
return;
|
||||
}
|
||||
|
||||
viewMode = "menu";
|
||||
console.log("[CO Main Menu] Exiting settings mode");
|
||||
window.CommonwealthOnlineUI.onBack();
|
||||
|
||||
settingsController.hide();
|
||||
|
||||
hideSettingsPanelAnimated(function () {
|
||||
if (mainPromptBar) {
|
||||
mainPromptBar.hidden = false;
|
||||
}
|
||||
setSelected(SETTINGS_INDEX);
|
||||
});
|
||||
}
|
||||
|
||||
settingsController = window.CO_Settings.create({
|
||||
mount: settingsPanel,
|
||||
promptBar: settingsPromptBar,
|
||||
bridge: window.CommonwealthOnlineUI,
|
||||
layout: "embedded",
|
||||
onBack: function () {
|
||||
exitSettingsMode();
|
||||
}
|
||||
});
|
||||
|
||||
// ── Keyboard navigation ────────────────────────────────────────
|
||||
|
||||
function handleMenuKeydown(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 Main Menu] Back");
|
||||
window.CommonwealthOnlineUI.onBack();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (viewMode === "settings") {
|
||||
settingsController.handleKeydown(event);
|
||||
return;
|
||||
}
|
||||
handleMenuKeydown(event);
|
||||
});
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Commonwealth Online — Main Menu Shell</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<style>
|
||||
/* Shell-only: left panel is layout chrome with no logo or menu content */
|
||||
.left-panel--shell {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-screen" id="mainScreen">
|
||||
<!-- Title-screen scenic background -->
|
||||
<div class="scene-layer" aria-hidden="true"></div>
|
||||
<div class="scene-overlay" aria-hidden="true"></div>
|
||||
|
||||
<!-- CRT / HUD texture overlays -->
|
||||
<div class="screen-effects" aria-hidden="true"></div>
|
||||
|
||||
<!-- Left panel chrome (no logo, no menu items) -->
|
||||
<aside class="left-panel left-panel--shell" aria-hidden="true"></aside>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,408 @@
|
||||
/* Commonwealth Online — main menu concept (1920×1080 baseline) */
|
||||
|
||||
@font-face {
|
||||
font-family: "Futura PT Demibold";
|
||||
src: url("../../fonts/futura-pt-demibold.otf") format("opentype");
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--co-ui-scale: 1;
|
||||
|
||||
--co-black: #0a0c0a;
|
||||
--co-panel-bg: rgba(8, 14, 10, 0.72);
|
||||
--co-panel-fade: rgba(8, 14, 10, 0);
|
||||
--co-green: #3ecf4a;
|
||||
--co-green-dim: rgba(62, 207, 74, 0.35);
|
||||
--co-amber: #c9a84a;
|
||||
--co-amber-muted: rgba(201, 168, 74, 0.55);
|
||||
--co-text-pale: #d8ddd0;
|
||||
--co-text-muted: #9aa898;
|
||||
--co-select-yellow: #c4a84b;
|
||||
--co-select-yellow-bright: #d4bc62;
|
||||
--co-select-text: #1a1c18;
|
||||
--co-border: rgba(201, 168, 74, 0.45);
|
||||
|
||||
--co-font: "Futura PT Demibold", "Roboto Condensed", "Bahnschrift", "Arial Narrow", Arial, sans-serif;
|
||||
--co-transition: 120ms ease-out;
|
||||
|
||||
--co-panel-width: 28%;
|
||||
--co-menu-size: calc(26px * var(--co-ui-scale));
|
||||
--co-menu-gap: calc(6px * var(--co-ui-scale));
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
font-family: var(--co-font);
|
||||
font-weight: 600;
|
||||
color: var(--co-text-pale);
|
||||
background: var(--co-black);
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* ── Root layout ─────────────────────────────────────────────── */
|
||||
|
||||
.main-screen {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ── Title-screen scenic background ──────────────────────────── */
|
||||
|
||||
.scene-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse 80% 60% at 72% 42%,
|
||||
rgba(62, 207, 74, 0.08) 0%,
|
||||
transparent 55%
|
||||
),
|
||||
radial-gradient(
|
||||
ellipse 55% 45% at 35% 70%,
|
||||
rgba(201, 168, 74, 0.06) 0%,
|
||||
transparent 50%
|
||||
),
|
||||
linear-gradient(
|
||||
165deg,
|
||||
#0b120d 0%,
|
||||
#141a12 28%,
|
||||
#1a2218 48%,
|
||||
#121810 72%,
|
||||
#080c08 100%
|
||||
);
|
||||
filter: brightness(0.82) contrast(1.05) saturate(0.65);
|
||||
}
|
||||
|
||||
.scene-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(6, 10, 8, 0.82) 0%,
|
||||
rgba(6, 10, 8, 0.38) 28%,
|
||||
rgba(6, 10, 8, 0.12) 58%,
|
||||
rgba(6, 10, 8, 0.22) 100%
|
||||
),
|
||||
rgba(4, 8, 6, 0.15);
|
||||
}
|
||||
|
||||
/* ── Screen effects: vignette, scanlines, HUD grid ─────────── */
|
||||
|
||||
.screen-effects {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.screen-effects::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(
|
||||
ellipse at center,
|
||||
transparent 40%,
|
||||
rgba(0, 0, 0, 0.55) 100%
|
||||
);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.screen-effects::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0, 0, 0, 0.12) 2px,
|
||||
rgba(0, 0, 0, 0.12) 3px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
90deg,
|
||||
transparent,
|
||||
transparent 79px,
|
||||
rgba(62, 207, 74, 0.03) 79px,
|
||||
rgba(62, 207, 74, 0.03) 80px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 79px,
|
||||
rgba(62, 207, 74, 0.03) 79px,
|
||||
rgba(62, 207, 74, 0.03) 80px
|
||||
);
|
||||
opacity: 0.65;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
|
||||
.main-screen::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 20;
|
||||
pointer-events: none;
|
||||
background:
|
||||
url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.08'/%3E%3C/svg%3E");
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
/* ── Left panel ──────────────────────────────────────────────── */
|
||||
|
||||
.left-panel {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
width: var(--co-panel-width);
|
||||
min-width: calc(280px * var(--co-ui-scale));
|
||||
max-width: 36%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding:
|
||||
calc(28px * var(--co-ui-scale))
|
||||
calc(32px * var(--co-ui-scale))
|
||||
calc(72px * var(--co-ui-scale))
|
||||
calc(28px * var(--co-ui-scale));
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
var(--co-panel-bg) 0%,
|
||||
rgba(8, 14, 10, 0.55) 70%,
|
||||
var(--co-panel-fade) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.brand-logo {
|
||||
width: calc(200px * var(--co-ui-scale));
|
||||
height: auto;
|
||||
margin-bottom: calc(36px * var(--co-ui-scale));
|
||||
opacity: 0.88;
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.5));
|
||||
}
|
||||
|
||||
/* ── Menu list ───────────────────────────────────────────────── */
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--co-menu-gap);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(14px * var(--co-ui-scale));
|
||||
width: 100%;
|
||||
padding:
|
||||
calc(10px * var(--co-ui-scale))
|
||||
calc(12px * var(--co-ui-scale));
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--co-text-muted);
|
||||
font-family: inherit;
|
||||
font-size: var(--co-menu-size);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
color var(--co-transition),
|
||||
transform var(--co-transition);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.menu-item__label {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.menu-item__bar {
|
||||
position: absolute;
|
||||
left: calc(-28px * var(--co-ui-scale));
|
||||
top: 50%;
|
||||
transform: translateY(-50%) scaleX(0);
|
||||
transform-origin: left center;
|
||||
width: calc(100% + 80px * var(--co-ui-scale));
|
||||
height: calc(100% + 4px);
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--co-select-yellow) 0%,
|
||||
var(--co-select-yellow-bright) 85%,
|
||||
#e0cc6e 100%
|
||||
);
|
||||
clip-path: polygon(
|
||||
0 0,
|
||||
calc(100% - 18px) 0,
|
||||
100% 50%,
|
||||
calc(100% - 18px) 100%,
|
||||
0 100%
|
||||
);
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity var(--co-transition),
|
||||
transform var(--co-transition);
|
||||
z-index: 1;
|
||||
box-shadow: 0 0 12px rgba(196, 168, 75, 0.25);
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
color: var(--co-text-pale);
|
||||
}
|
||||
|
||||
.menu-item:hover .menu-item__bar {
|
||||
opacity: 1;
|
||||
transform: translateY(-50%) scaleX(1);
|
||||
}
|
||||
|
||||
.menu-item:focus-visible {
|
||||
color: var(--co-text-pale);
|
||||
outline: 1px solid var(--co-green-dim);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.menu-item.is-selected {
|
||||
color: var(--co-select-text);
|
||||
}
|
||||
|
||||
.menu-item.is-selected .menu-item__bar {
|
||||
opacity: 1;
|
||||
transform: translateY(-50%) scaleX(1);
|
||||
}
|
||||
|
||||
.menu-item.is-disabled {
|
||||
opacity: 0.38;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Bottom prompt bar ───────────────────────────────────────── */
|
||||
|
||||
.prompt-bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: calc(40px * var(--co-ui-scale));
|
||||
padding:
|
||||
calc(10px * var(--co-ui-scale))
|
||||
calc(24px * var(--co-ui-scale));
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
transparent 0%,
|
||||
rgba(6, 10, 8, 0.75) 30%,
|
||||
rgba(6, 10, 8, 0.88) 100%
|
||||
);
|
||||
border-top: 1px solid rgba(201, 168, 74, 0.15);
|
||||
transition: opacity 200ms ease-out, visibility 200ms ease-out;
|
||||
}
|
||||
|
||||
.prompt-bar[hidden] {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.prompt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(8px * var(--co-ui-scale));
|
||||
}
|
||||
|
||||
.prompt__glyph {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: calc(22px * var(--co-ui-scale));
|
||||
height: calc(22px * var(--co-ui-scale));
|
||||
font-size: calc(11px * var(--co-ui-scale));
|
||||
font-weight: 700;
|
||||
color: var(--co-amber);
|
||||
border: 1px solid var(--co-amber-muted);
|
||||
border-radius: 50%;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.prompt__glyph--icon {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
min-width: calc(28px * var(--co-ui-scale));
|
||||
width: calc(28px * var(--co-ui-scale));
|
||||
height: calc(28px * var(--co-ui-scale));
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.prompt__glyph-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prompt__label {
|
||||
font-size: calc(11px * var(--co-ui-scale));
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.16em;
|
||||
color: var(--co-text-pale);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Settings mode: keep main nav, dim background more */
|
||||
|
||||
.main-screen.is-settings-mode .left-panel {
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(8, 14, 10, 0.9) 0%,
|
||||
rgba(8, 14, 10, 0.78) 65%,
|
||||
rgba(8, 14, 10, 0.15) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.main-screen.is-settings-mode .menu-item__bar {
|
||||
width: calc(100% + 28px * var(--co-ui-scale));
|
||||
}
|
||||
|
||||
.main-screen.is-settings-mode .scene-overlay {
|
||||
background:
|
||||
linear-gradient(
|
||||
90deg,
|
||||
rgba(6, 10, 8, 0.88) 0%,
|
||||
rgba(6, 10, 8, 0.55) 24%,
|
||||
rgba(6, 10, 8, 0.4) 55%,
|
||||
rgba(6, 10, 8, 0.48) 100%
|
||||
),
|
||||
rgba(4, 8, 6, 0.32);
|
||||
}
|
||||
@@ -29,7 +29,7 @@
|
||||
<aside class="left-panel">
|
||||
<img
|
||||
class="brand-logo"
|
||||
src="assets/logo.svg"
|
||||
src="../shared/logo.svg"
|
||||
alt="Commonwealth Online"
|
||||
width="220"
|
||||
height="54"
|
||||
@@ -93,13 +93,13 @@
|
||||
<footer class="prompt-bar" id="pausePromptBar" aria-label="Controller prompts">
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_A_White_Alt.png" src="./assets/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_A_White_Alt.png" src="../shared/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">SELECT</span>
|
||||
</div>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_B_White_Alt.png" src="./assets/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_B_White_Alt.png" src="../shared/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">BACK</span>
|
||||
</div>
|
||||
@@ -117,13 +117,13 @@
|
||||
</div>
|
||||
<div class="prompt" data-prompt="select">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_A_White_Alt.png" src="./assets/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_A_White_Alt.png" src="../shared/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">SELECT</span>
|
||||
</div>
|
||||
<div class="prompt" data-prompt="back">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_B_White_Alt.png" src="./assets/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_B_White_Alt.png" src="../shared/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">BACK</span>
|
||||
</div>
|
||||
@@ -143,25 +143,25 @@
|
||||
<footer class="prompt-bar prompt-bar--map-mode" id="mapPromptBar" aria-label="Map mode prompts" hidden>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon prompt__glyph--icon-stick" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_L_2D_Alt.png" src="./assets/icons/Xbox/T_X_L_2D_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_L_2D_Alt.png" src="../shared/icons/Xbox/T_X_L_2D_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">MOVE</span>
|
||||
</div>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon prompt__glyph--icon-stick" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_R_Y_Alt.png" src="./assets/icons/Xbox/T_X_R_Y_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_R_Y_Alt.png" src="../shared/icons/Xbox/T_X_R_Y_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">ZOOM</span>
|
||||
</div>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_A_White_Alt.png" src="./assets/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_A_White_Alt.png" src="../shared/icons/Xbox/T_X_A_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">SELECT</span>
|
||||
</div>
|
||||
<div class="prompt">
|
||||
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||
<img class="prompt__glyph-img" data-glyph-src="./assets/icons/Xbox/T_X_B_White_Alt.png" src="./assets/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
<img class="prompt__glyph-img" data-glyph-src="../shared/icons/Xbox/T_X_B_White_Alt.png" src="../shared/icons/Xbox/T_X_B_White_Alt.png" alt="" draggable="false">
|
||||
</span>
|
||||
<span class="prompt__label">BACK</span>
|
||||
</div>
|
||||
|
||||
@@ -479,7 +479,7 @@ body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Xbox PNG glyphs (from ui/Icons); white pixels are tinted amber in script.js */
|
||||
/* Xbox PNG glyphs (from shared/icons); white pixels are tinted amber in script.js */
|
||||
.prompt__glyph--icon {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user