(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); } }; // ── Pointer mode API for C++ invocation ───────────────────────── window.CO_App = { setPointerMode: function (mode) { console.log("[CO Main Menu] Pointer mode:", mode); } }; // ── Controller input handler ───────────────────────────────────── window.handleControllerInput = function (button, phase) { if (phase === "down") { switch (button) { case "dpad_up": moveSelection(-1); break; case "dpad_down": moveSelection(1); break; case "a": activateSelected(); break; case "b": if (viewMode === "settings") { exitSettingsMode(); } break; } } }; // ── 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); }); })();