Pause menu: shared map blur, PC back, Xbox glyphs
Wrap map art and markers in a new .map-world so blur, pan and zoom animate together; add a PC-facing top-left ESC BACK button and a map-mode prompt bar using Xbox PNG glyphs. Implement JS glyph-tinting, map-mode enter/exit logic, and pan/zoom handlers; update styles for .map-world, markers, prompt bars, and the PC back button. Add Xbox icon assets and update deploy/stage scripts to copy them. Update changelog and dev-log entries to document the changes.
@@ -10,14 +10,19 @@ For testing notes, milestone summaries, known issues, and next steps, see [`docs
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Dedicated PC back button on the pause menu map (`ESC` keycap + BACK label, top-left) so mouse users can leave map mode without relying on the controller B prompt. Fades in after the left panel hides and fades out immediately on exit so it does not overlap the logo.
|
||||||
- Static HTML/CSS/JS pause menu concept prototype at `ui/views/CommonwealthOnline/pause-menu/` — Fallout 4–styled multiplayer map pause screen with left menu, server info panel, controller prompts, and keyboard navigation (MAP selected by default).
|
- Static HTML/CSS/JS pause menu concept prototype at `ui/views/CommonwealthOnline/pause-menu/` — Fallout 4–styled multiplayer map pause screen with left menu, server info panel, controller prompts, and keyboard navigation (MAP selected by default).
|
||||||
- Mouse wheel zoom and click-drag pan on the pause menu map background (`ui/views/CommonwealthOnline/pause-menu/script.js`), previewing the intended Fallout 76-style navigable map; markers move with the map via shared CSS custom properties.
|
- Mouse wheel zoom and click-drag pan on the pause menu map background (`ui/views/CommonwealthOnline/pause-menu/script.js`), previewing the intended Fallout 76-style navigable map; markers move with the map via shared CSS custom properties.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Pause menu player/ally map markers share the map's blur filter (via a `.map-world` wrapper) so they blur and unblur in lockstep with the map instead of fading on a separate timing.
|
||||||
|
- Pause menu controller prompts: map navigation prompts (MOVE, ZOOM, SELECT, BACK) now appear on the map mode bar instead of the pause menu bar; pause menu shows SELECT and BACK only.
|
||||||
- Pause menu prototype's `--co-ui-scale` formula (`ui/views/CommonwealthOnline/pause-menu/styles.css`) divided a length by a bare number (`calc(100vw / 1920)`), which is a different CSS type than the plain numbers used alongside it in `clamp()`/`min()`. That type mismatch made the whole custom property invalid at the point of use, so every `calc(Npx * var(--co-ui-scale))` silently fell back to `auto` — causing the logo, menu items, and selection bar to stretch to fill their containers instead of their intended sizes. Fixed by dividing by a length (`1920px`/`1080px`) so the result is a valid unitless ratio.
|
- Pause menu prototype's `--co-ui-scale` formula (`ui/views/CommonwealthOnline/pause-menu/styles.css`) divided a length by a bare number (`calc(100vw / 1920)`), which is a different CSS type than the plain numbers used alongside it in `clamp()`/`min()`. That type mismatch made the whole custom property invalid at the point of use, so every `calc(Npx * var(--co-ui-scale))` silently fell back to `auto` — causing the logo, menu items, and selection bar to stretch to fill their containers instead of their intended sizes. Fixed by dividing by a length (`1920px`/`1080px`) so the result is a valid unitless ratio.
|
||||||
- Pause menu map background switched from a cropped `cover` fit to `contain` so the full regional map is visible (less zoomed-in) instead of a tightly cropped strip, closer to a Fallout 76-style full map overview.
|
- Pause menu map background switched from a cropped `cover` fit to `contain` so the full regional map is visible (less zoomed-in) instead of a tightly cropped strip, closer to a Fallout 76-style full map overview.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
- Map mode controller prompts now use LS for MOVE and RS for ZOOM (replacing the incorrect Menu glyph for move), with A SELECT and B BACK unchanged.
|
||||||
|
- Pause menu and map mode controller prompts now use Xbox PNG glyphs from `ui/Icons/Xbox` (amber-tinted like the server browser): LS (`T_X_L_2D_Alt`) MOVE, RS Y-axis (`T_X_R_Y_Alt`) ZOOM, A SELECT, B BACK.
|
||||||
- Rewrote root `README.md` for current project stage: structured status (working / in progress / planned / out of scope), architecture summary, developer quick start, updated repository layout and requirements, honest scale limits (4 proxies per client vs 16+ design target), and aligned license/legal sections with `LICENSE`.
|
- Rewrote root `README.md` for current project stage: structured status (working / in progress / planned / out of scope), architecture summary, developer quick start, updated repository layout and requirements, honest scale limits (4 proxies per client vs 16+ design target), and aligned license/legal sections with `LICENSE`.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ if errorlevel 1 (
|
|||||||
echo Failed to copy controller icons.
|
echo Failed to copy controller icons.
|
||||||
exit /b 1
|
exit /b 1
|
||||||
)
|
)
|
||||||
|
if not exist "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons" mkdir "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons"
|
||||||
|
xcopy /Y /E /I "%SCRIPT_DIR%ui\Icons\*" "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons\"
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo Failed to copy pause-menu controller icons.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
if not "%~2"=="" (
|
if not "%~2"=="" (
|
||||||
set "PLUGIN_DLL=%~2"
|
set "PLUGIN_DLL=%~2"
|
||||||
|
|||||||
@@ -7,6 +7,65 @@ failed experiments, successful tests, and next steps.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-04 - Pause Menu Markers Share Map Blur
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Player and ally map markers were either sharp on the pause blur or fading on a separate opacity timing. Map art and markers now live under one `.map-world` wrapper that owns the blur/pan/zoom, so markers animate as part of the map.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/index.html`
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/styles.css`
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/script.js`
|
||||||
|
- `changelog.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Wrapped `.map-layer` and `.map-markers` in `#mapWorld`
|
||||||
|
- Filter and transform apply once on `.map-world` (`filter 200ms ease-out`)
|
||||||
|
- Map mode toggles `is-blurred` / `is-interactive` on the wrapper; pan/zoom listeners attach there too
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Open `ui/views/CommonwealthOnline/pause-menu/index.html` in a browser
|
||||||
|
- On the pause screen, markers should only read as soft map detail under the blur (not sharp overlays)
|
||||||
|
- Enter/exit MAP and confirm markers unblur and blur with the map, not ahead of it
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- None currently known
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Integrate into in-game pause flow via PrismaUI_F4 HTML layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-07-04 - Pause Menu Map PC Back Button
|
||||||
|
|
||||||
|
### Summary
|
||||||
|
Added a clear PC-facing back control on the pause menu map. The bottom B BACK prompt stays as a passive controller hint; mouse users get a dedicated top-left button with an ESC keycap and BACK label.
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/index.html`
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/styles.css`
|
||||||
|
- `ui/views/CommonwealthOnline/pause-menu/script.js`
|
||||||
|
- `changelog.md`
|
||||||
|
|
||||||
|
### Details
|
||||||
|
- Map mode shows a panel-styled `map-pc-back` button (top-left) that calls `exitMapMode`
|
||||||
|
- Button fades in after the left panel (logo) finishes hiding (200ms delay), and fades out immediately on exit so it never overlaps the logo
|
||||||
|
- Controller prompt bar BACK is a non-interactive prompt again (matches MOVE / ZOOM / SELECT)
|
||||||
|
- Escape key already exits map mode; the button mirrors that for pointer users
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- Open `ui/views/CommonwealthOnline/pause-menu/index.html` in a browser
|
||||||
|
- Click MAP, confirm the top-left ESC BACK button appears and the bottom bar still shows B BACK
|
||||||
|
- Click the PC button (or press Escape) and confirm the pause menu returns
|
||||||
|
|
||||||
|
### Known Issues
|
||||||
|
- None currently known
|
||||||
|
|
||||||
|
### Next Steps
|
||||||
|
- Integrate into in-game pause flow via PrismaUI_F4 HTML layer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-03 - Pause Menu UI Concept Prototype
|
## 2026-07-03 - Pause Menu UI Concept Prototype
|
||||||
|
|
||||||
### Summary
|
### Summary
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ xcopy /Y /E /I /Q "%SCRIPT_DIR%ui\views\*" "%VIEWS_DIR%\" >nul
|
|||||||
echo PrismaUI controller icons
|
echo PrismaUI controller icons
|
||||||
if not exist "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons" mkdir "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons"
|
if not exist "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons" mkdir "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons"
|
||||||
xcopy /Y /E /I /Q "%SCRIPT_DIR%ui\Icons\*" "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons\" >nul
|
xcopy /Y /E /I /Q "%SCRIPT_DIR%ui\Icons\*" "%VIEWS_DIR%\CommonwealthOnline\browser\assets\icons\" >nul
|
||||||
|
if not exist "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons" mkdir "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons"
|
||||||
|
xcopy /Y /E /I /Q "%SCRIPT_DIR%ui\Icons\*" "%VIEWS_DIR%\CommonwealthOnline\pause-menu\assets\icons\" >nul
|
||||||
|
|
||||||
if exist "%SCRIPT_DIR%ui\views\CommonwealthOnline\browser\assets\F4MouseCursor.png" (
|
if exist "%SCRIPT_DIR%ui\views\CommonwealthOnline\browser\assets\F4MouseCursor.png" (
|
||||||
echo PrismaUI_F4\misc\cursor.png
|
echo PrismaUI_F4\misc\cursor.png
|
||||||
|
|||||||
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
@@ -8,18 +8,18 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="pause-screen" id="pauseScreen">
|
<div class="pause-screen" id="pauseScreen">
|
||||||
<!-- Map background -->
|
<!-- Map + markers share one filter so blur/unblur stays in lockstep -->
|
||||||
<div class="map-layer" aria-hidden="true"></div>
|
<div class="map-world" id="mapWorld" aria-hidden="true">
|
||||||
<div class="map-overlay" aria-hidden="true"></div>
|
<div class="map-layer"></div>
|
||||||
|
<div class="map-markers">
|
||||||
<!-- Subtle multiplayer markers on the map -->
|
|
||||||
<div class="map-markers" aria-hidden="true">
|
|
||||||
<span class="marker marker--player" style="left: 58%; top: 42%;"></span>
|
<span class="marker marker--player" style="left: 58%; top: 42%;"></span>
|
||||||
<span class="marker marker--ally" style="left: 64%; top: 38%;"></span>
|
<span class="marker marker--ally" style="left: 64%; top: 38%;"></span>
|
||||||
<span class="marker marker--ally" style="left: 52%; top: 48%;"></span>
|
<span class="marker marker--ally" style="left: 52%; top: 48%;"></span>
|
||||||
<span class="marker marker--ally" style="left: 71%; top: 55%;"></span>
|
<span class="marker marker--ally" style="left: 71%; top: 55%;"></span>
|
||||||
<span class="marker marker--ally" style="left: 45%; top: 36%;"></span>
|
<span class="marker marker--ally" style="left: 45%; top: 36%;"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="map-overlay" aria-hidden="true"></div>
|
||||||
|
|
||||||
<!-- CRT / HUD texture overlays -->
|
<!-- CRT / HUD texture overlays -->
|
||||||
<div class="screen-effects" aria-hidden="true"></div>
|
<div class="screen-effects" aria-hidden="true"></div>
|
||||||
@@ -85,22 +85,52 @@
|
|||||||
<p class="server-panel__players">12 / 24</p>
|
<p class="server-panel__players">12 / 24</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Bottom controller prompts -->
|
<!-- Bottom controller prompts (pause menu) -->
|
||||||
<footer class="prompt-bar" aria-label="Controller prompts">
|
<footer class="prompt-bar" id="pausePromptBar" aria-label="Controller prompts">
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<span class="prompt__glyph" aria-hidden="true">☰</span>
|
<span class="prompt__glyph prompt__glyph--icon" aria-hidden="true">
|
||||||
<span class="prompt__label">MOVE</span>
|
<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">
|
||||||
</div>
|
</span>
|
||||||
<div class="prompt">
|
|
||||||
<span class="prompt__glyph prompt__glyph--rs" aria-hidden="true">RS</span>
|
|
||||||
<span class="prompt__label">ZOOM</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt">
|
|
||||||
<span class="prompt__glyph prompt__glyph--round" aria-hidden="true">A</span>
|
|
||||||
<span class="prompt__label">SELECT</span>
|
<span class="prompt__label">SELECT</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="prompt">
|
<div class="prompt">
|
||||||
<span class="prompt__glyph prompt__glyph--round" aria-hidden="true">B</span>
|
<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">
|
||||||
|
</span>
|
||||||
|
<span class="prompt__label">BACK</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<!-- PC back button (map mode only) -->
|
||||||
|
<button type="button" class="map-pc-back" id="mapBackButton" aria-hidden="true">
|
||||||
|
<span class="map-pc-back__key" aria-hidden="true">ESC</span>
|
||||||
|
<span class="map-pc-back__label">BACK</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Map mode prompt bar (controller: LS MOVE / RS ZOOM / A SELECT / B BACK) -->
|
||||||
|
<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">
|
||||||
|
</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">
|
||||||
|
</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">
|
||||||
|
</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">
|
||||||
|
</span>
|
||||||
<span class="prompt__label">BACK</span>
|
<span class="prompt__label">BACK</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -21,6 +21,120 @@
|
|||||||
updateScale();
|
updateScale();
|
||||||
window.addEventListener("resize", updateScale);
|
window.addEventListener("resize", updateScale);
|
||||||
|
|
||||||
|
// ── Tint Xbox prompt glyphs to pause-menu amber (same approach as browser) ──
|
||||||
|
// White pixels become --co-amber; black stays black; transparent stays transparent.
|
||||||
|
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) {
|
||||||
|
// file:// or cross-origin can taint the canvas; keep the original white glyph.
|
||||||
|
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();
|
||||||
|
|
||||||
var menu = document.getElementById("menu");
|
var menu = document.getElementById("menu");
|
||||||
if (!menu) {
|
if (!menu) {
|
||||||
return;
|
return;
|
||||||
@@ -30,11 +144,11 @@
|
|||||||
var selectedIndex = -1; // No default selection
|
var selectedIndex = -1; // No default selection
|
||||||
|
|
||||||
function setSelected(index) {
|
function setSelected(index) {
|
||||||
if (index < 0 || index >= items.length) {
|
if (index >= items.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedIndex = index;
|
selectedIndex = index < 0 ? -1 : index;
|
||||||
|
|
||||||
items.forEach(function (item, i) {
|
items.forEach(function (item, i) {
|
||||||
var isSelected = i === selectedIndex;
|
var isSelected = i === selectedIndex;
|
||||||
@@ -55,8 +169,12 @@
|
|||||||
|
|
||||||
if (action === "resume") {
|
if (action === "resume") {
|
||||||
console.log("[CO Pause Menu] Resume game (concept)");
|
console.log("[CO Pause Menu] Resume game (concept)");
|
||||||
|
setSelected(-1);
|
||||||
|
} else if (action === "map") {
|
||||||
|
enterMapMode();
|
||||||
} else if (action === "quit") {
|
} else if (action === "quit") {
|
||||||
console.log("[CO Pause Menu] Quit to main menu (concept)");
|
console.log("[CO Pause Menu] Quit to main menu (concept)");
|
||||||
|
setSelected(-1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +212,7 @@
|
|||||||
break;
|
break;
|
||||||
case "Escape":
|
case "Escape":
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.log("[CO Pause Menu] Back / close pause menu (concept)");
|
exitMapMode();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -105,13 +223,88 @@
|
|||||||
setSelected(selectedIndex);
|
setSelected(selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Map mode state management ───────────────────────────────────
|
||||||
|
var isMapMode = false;
|
||||||
|
|
||||||
|
function enterMapMode() {
|
||||||
|
isMapMode = true;
|
||||||
|
console.log("[CO Pause Menu] Entering map mode");
|
||||||
|
|
||||||
|
var leftPanel = document.querySelector(".left-panel");
|
||||||
|
var mapWorld = document.getElementById("mapWorld");
|
||||||
|
var pausePromptBar = document.getElementById("pausePromptBar");
|
||||||
|
var mapPromptBar = document.getElementById("mapPromptBar");
|
||||||
|
var mapBackButton = document.getElementById("mapBackButton");
|
||||||
|
var serverPanel = document.querySelector(".server-panel");
|
||||||
|
|
||||||
|
setSelected(-1);
|
||||||
|
if (leftPanel) leftPanel.classList.add("is-hidden");
|
||||||
|
if (serverPanel) serverPanel.style.display = "none";
|
||||||
|
if (mapWorld) {
|
||||||
|
mapWorld.classList.remove("is-blurred");
|
||||||
|
mapWorld.classList.add("is-interactive");
|
||||||
|
}
|
||||||
|
if (pausePromptBar) pausePromptBar.hidden = true;
|
||||||
|
if (mapPromptBar) mapPromptBar.hidden = false;
|
||||||
|
if (mapBackButton) {
|
||||||
|
mapBackButton.classList.add("is-visible");
|
||||||
|
mapBackButton.setAttribute("aria-hidden", "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function exitMapMode() {
|
||||||
|
if (!isMapMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isMapMode = false;
|
||||||
|
console.log("[CO Pause Menu] Exiting map mode");
|
||||||
|
|
||||||
|
var leftPanel = document.querySelector(".left-panel");
|
||||||
|
var mapWorld = document.getElementById("mapWorld");
|
||||||
|
var pausePromptBar = document.getElementById("pausePromptBar");
|
||||||
|
var mapPromptBar = document.getElementById("mapPromptBar");
|
||||||
|
var mapBackButton = document.getElementById("mapBackButton");
|
||||||
|
var serverPanel = document.querySelector(".server-panel");
|
||||||
|
|
||||||
|
if (mapBackButton) {
|
||||||
|
mapBackButton.classList.remove("is-visible");
|
||||||
|
mapBackButton.setAttribute("aria-hidden", "true");
|
||||||
|
}
|
||||||
|
if (leftPanel) leftPanel.classList.remove("is-hidden");
|
||||||
|
if (serverPanel) serverPanel.style.display = "block";
|
||||||
|
if (mapWorld) {
|
||||||
|
mapWorld.classList.add("is-blurred");
|
||||||
|
mapWorld.classList.remove("is-interactive");
|
||||||
|
}
|
||||||
|
if (pausePromptBar) pausePromptBar.hidden = false;
|
||||||
|
if (mapPromptBar) mapPromptBar.hidden = true;
|
||||||
|
|
||||||
|
setSelected(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Blur map initially ────────────────────────────────────────
|
||||||
|
var initialMapWorld = document.getElementById("mapWorld");
|
||||||
|
if (initialMapWorld) {
|
||||||
|
initialMapWorld.classList.add("is-blurred");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Map mode PC back button handler ───────────────────────────
|
||||||
|
var mapBackButton = document.getElementById("mapBackButton");
|
||||||
|
if (mapBackButton) {
|
||||||
|
mapBackButton.addEventListener("click", function (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
exitMapMode();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ── Map pan/zoom (concept preview of future Fallout 76-style navigation) ──
|
// ── Map pan/zoom (concept preview of future Fallout 76-style navigation) ──
|
||||||
// Scroll to zoom, click-drag to pan once zoomed in. Real implementation
|
// Scroll to zoom, click-drag to pan once zoomed in. Real implementation
|
||||||
// will eventually read controller right-stick input via F4SE instead.
|
// will eventually read controller right-stick input via F4SE instead.
|
||||||
var pauseScreen = document.getElementById("pauseScreen");
|
var pauseScreen = document.getElementById("pauseScreen");
|
||||||
var mapLayer = document.querySelector(".map-layer");
|
var mapWorld = document.getElementById("mapWorld");
|
||||||
|
|
||||||
if (pauseScreen && mapLayer) {
|
if (pauseScreen && mapWorld) {
|
||||||
var MAX_ZOOM = 4;
|
var MAX_ZOOM = 4;
|
||||||
var ZOOM_STEP = 0.18;
|
var ZOOM_STEP = 0.18;
|
||||||
var zoom = 1;
|
var zoom = 1;
|
||||||
@@ -201,7 +394,7 @@
|
|||||||
panY = Math.max(-maxPanY, Math.min(maxPanY, panY));
|
panY = Math.max(-maxPanY, Math.min(maxPanY, panY));
|
||||||
}
|
}
|
||||||
|
|
||||||
mapLayer.addEventListener(
|
mapWorld.addEventListener(
|
||||||
"wheel",
|
"wheel",
|
||||||
function (event) {
|
function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -218,7 +411,7 @@
|
|||||||
{ passive: false }
|
{ passive: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
mapLayer.addEventListener("mousedown", function (event) {
|
mapWorld.addEventListener("mousedown", function (event) {
|
||||||
if (zoom <= MIN_ZOOM) {
|
if (zoom <= MIN_ZOOM) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,12 +69,37 @@ body {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Map layers ──────────────────────────────────────────────── */
|
/* ── Map world (map art + markers, one shared blur/pan/zoom) ── */
|
||||||
|
|
||||||
|
.map-world {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
filter: brightness(0.74) contrast(1.04) saturate(0.55);
|
||||||
|
transform: translate(var(--map-pan-x, 0px), var(--map-pan-y, 0px))
|
||||||
|
scale(var(--map-zoom, 1));
|
||||||
|
transform-origin: center center;
|
||||||
|
transition: transform 160ms ease-out, filter 200ms ease-out;
|
||||||
|
cursor: grab;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-world.is-interactive {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-world.is-blurred {
|
||||||
|
filter: brightness(0.74) contrast(1.04) saturate(0.55) blur(12px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pause-screen.is-panning .map-world {
|
||||||
|
transition: none;
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
.map-layer {
|
.map-layer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 1;
|
|
||||||
/*
|
/*
|
||||||
* "contain" keeps the whole regional map visible (Fallout 76-style full
|
* "contain" keeps the whole regional map visible (Fallout 76-style full
|
||||||
* overview) instead of "cover" cropping it down to a tight, zoomed-in
|
* overview) instead of "cover" cropping it down to a tight, zoomed-in
|
||||||
@@ -84,12 +109,6 @@ body {
|
|||||||
background:
|
background:
|
||||||
url("assets/map.png") center center / contain no-repeat;
|
url("assets/map.png") center center / contain no-repeat;
|
||||||
background-color: #0b100c;
|
background-color: #0b100c;
|
||||||
filter: brightness(0.74) contrast(1.04) saturate(0.55);
|
|
||||||
transform: translate(var(--map-pan-x, 0px), var(--map-pan-y, 0px))
|
|
||||||
scale(var(--map-zoom, 1));
|
|
||||||
transform-origin: center center;
|
|
||||||
transition: transform 160ms ease-out;
|
|
||||||
cursor: grab;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.map-overlay {
|
.map-overlay {
|
||||||
@@ -109,24 +128,12 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ── Map markers ───────────────────────────────────────────── */
|
/* ── Map markers ───────────────────────────────────────────── */
|
||||||
/* Shares the same pan/zoom transform (inherited custom properties) as
|
/* Inside .map-world so they share the same filter and transform as the map. */
|
||||||
.map-layer so markers stay pinned to their map position while navigating. */
|
|
||||||
|
|
||||||
.map-markers {
|
.map-markers {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 3;
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transform: translate(var(--map-pan-x, 0px), var(--map-pan-y, 0px))
|
|
||||||
scale(var(--map-zoom, 1));
|
|
||||||
transform-origin: center center;
|
|
||||||
transition: transform 160ms ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pause-screen.is-panning .map-layer,
|
|
||||||
.pause-screen.is-panning .map-markers {
|
|
||||||
transition: none;
|
|
||||||
cursor: grabbing;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker {
|
.marker {
|
||||||
@@ -243,6 +250,12 @@ body {
|
|||||||
rgba(8, 14, 10, 0.55) 70%,
|
rgba(8, 14, 10, 0.55) 70%,
|
||||||
var(--co-panel-fade) 100%
|
var(--co-panel-fade) 100%
|
||||||
);
|
);
|
||||||
|
transition: opacity 200ms ease-out, visibility 200ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-panel.is-hidden {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.brand-logo {
|
.brand-logo {
|
||||||
@@ -321,11 +334,11 @@ body {
|
|||||||
box-shadow: 0 0 12px rgba(196, 168, 75, 0.25);
|
box-shadow: 0 0 12px rgba(196, 168, 75, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover:not(.is-selected) {
|
.menu-item:hover {
|
||||||
color: var(--co-text-pale);
|
color: var(--co-text-pale);
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item:hover:not(.is-selected) .menu-item__bar {
|
.menu-item:hover .menu-item__bar {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateY(-50%) scaleX(1);
|
transform: translateY(-50%) scaleX(1);
|
||||||
}
|
}
|
||||||
@@ -431,6 +444,16 @@ body {
|
|||||||
rgba(6, 10, 8, 0.88) 100%
|
rgba(6, 10, 8, 0.88) 100%
|
||||||
);
|
);
|
||||||
border-top: 1px solid rgba(201, 168, 74, 0.15);
|
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-bar--map-mode {
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt {
|
.prompt {
|
||||||
@@ -454,15 +477,30 @@ body {
|
|||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt__glyph--rs {
|
/* Xbox PNG glyphs (from ui/Icons); white pixels are tinted amber in script.js */
|
||||||
border-radius: 4px;
|
.prompt__glyph--icon {
|
||||||
font-size: calc(9px * var(--co-ui-scale));
|
border: none;
|
||||||
letter-spacing: 0.02em;
|
border-radius: 0;
|
||||||
min-width: calc(26px * var(--co-ui-scale));
|
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--round {
|
/* Stick glyphs have more detail (arrows/rings) and read better slightly larger */
|
||||||
font-size: calc(10px * var(--co-ui-scale));
|
.prompt__glyph--icon-stick {
|
||||||
|
min-width: calc(34px * var(--co-ui-scale));
|
||||||
|
width: calc(34px * var(--co-ui-scale));
|
||||||
|
height: calc(34px * var(--co-ui-scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
.prompt__glyph-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prompt__label {
|
.prompt__label {
|
||||||
@@ -473,6 +511,96 @@ body {
|
|||||||
opacity: 0.85;
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Map mode PC back button ─────────────────────────────────── */
|
||||||
|
/*
|
||||||
|
* Fades in after the left panel (logo) finishes hiding (200ms), and
|
||||||
|
* fades out immediately on exit so it never sits on top of the logo.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.map-pc-back {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(28px * var(--co-ui-scale));
|
||||||
|
left: calc(28px * var(--co-ui-scale));
|
||||||
|
z-index: 12;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: calc(12px * var(--co-ui-scale));
|
||||||
|
padding:
|
||||||
|
calc(10px * var(--co-ui-scale))
|
||||||
|
calc(16px * var(--co-ui-scale));
|
||||||
|
border: 1px solid var(--co-border);
|
||||||
|
background: rgba(6, 12, 8, 0.82);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 16px rgba(0, 0, 0, 0.35),
|
||||||
|
0 2px 8px rgba(0, 0, 0, 0.35);
|
||||||
|
color: var(--co-text-pale);
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: calc(14px * var(--co-ui-scale));
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.16em;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
transition:
|
||||||
|
color var(--co-transition),
|
||||||
|
border-color var(--co-transition),
|
||||||
|
background var(--co-transition),
|
||||||
|
opacity 150ms ease-out,
|
||||||
|
visibility 0ms linear 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
pointer-events: auto;
|
||||||
|
transition:
|
||||||
|
color var(--co-transition),
|
||||||
|
border-color var(--co-transition),
|
||||||
|
background var(--co-transition),
|
||||||
|
opacity 200ms ease-out 200ms,
|
||||||
|
visibility 0ms linear 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back__key {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: calc(36px * var(--co-ui-scale));
|
||||||
|
padding:
|
||||||
|
calc(3px * var(--co-ui-scale))
|
||||||
|
calc(8px * var(--co-ui-scale));
|
||||||
|
border: 1px solid var(--co-amber-muted);
|
||||||
|
border-radius: 2px;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
color: var(--co-amber);
|
||||||
|
font-size: calc(11px * var(--co-ui-scale));
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back__label {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back:hover {
|
||||||
|
color: var(--co-green);
|
||||||
|
border-color: var(--co-green-dim);
|
||||||
|
background: rgba(8, 18, 10, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back:hover .map-pc-back__key {
|
||||||
|
color: var(--co-green);
|
||||||
|
border-color: var(--co-green-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-pc-back:focus-visible {
|
||||||
|
outline: 1px solid var(--co-green-dim);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Responsive tweaks ───────────────────────────────────────── */
|
/* ── Responsive tweaks ───────────────────────────────────────── */
|
||||||
/*
|
/*
|
||||||
* Sizing is already fluid via --co-ui-scale (see :root), which now scales
|
* Sizing is already fluid via --co-ui-scale (see :root), which now scales
|
||||||
|
|||||||