Revamp CommonwealthOnline browser UI and styles

Rework the CommonwealthOnline browser UI: restructure HTML into a nav + subpanel layout, add SVG/PNG/font assets and new sprites, and overhaul styles with a new font-face and refreshed color/spacing variables. Refactor components: tabs use a left nav list; filters extraction (renderRow) and reset focus handling; direct-connect renders into a dedicated container; server-list switched to compact row layout and adds a scroll indicator; server-details now uses a preview area and simplified preview/stats rendering. Update app logic: adjust focus zone handling (nextFocusZone), limit zones per tab, update connection status/count display, and avoid rendering details for direct-connect. Overall changes improve responsiveness, visual hierarchy, and focus/navigation behavior.
This commit is contained in:
2026-06-08 14:40:00 +12:00
parent fe620020be
commit cfd1fab6b2
13 changed files with 555 additions and 506 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

+36 -11
View File
@@ -6,7 +6,7 @@
var joinOverlay = document.getElementById("coJoinOverlay"); var joinOverlay = document.getElementById("coJoinOverlay");
var joinMessage = document.getElementById("coJoinMessage"); var joinMessage = document.getElementById("coJoinMessage");
var FOCUS_ZONES = ["tabs", "filters", "server-list", "details"]; var FOCUS_ZONES = ["tabs", "filters", "server-list"];
var REPEAT_INITIAL_MS = 400; var REPEAT_INITIAL_MS = 400;
var REPEAT_RATE_MS = 80; var REPEAT_RATE_MS = 80;
@@ -144,9 +144,14 @@
} }
function updateServerCount() { function updateServerCount() {
var el = document.getElementById("coServerCount"); var countEl = document.getElementById("coServerCount");
if (el) { var titleEl = document.getElementById("coSubpanelTitle");
el.textContent = " \u2014 " + state.servers.length + " servers found"; var count = state.servers.length;
if (countEl) {
countEl.textContent = count + " Found";
}
if (titleEl && state.activeTab === "server-browser") {
titleEl.textContent = count === 1 ? "1 Server" : count + " Servers";
} }
} }
@@ -223,7 +228,10 @@
CO_Tabs.render(root, state); CO_Tabs.render(root, state);
CO_ControllerPrompts.render(root, state); CO_ControllerPrompts.render(root, state);
document.getElementById("coConnectionStatus").textContent = state.connectionStatus; var statusEl = document.getElementById("coConnectionStatus");
if (statusEl) {
statusEl.textContent = String(state.connectionStatus || "Online").toUpperCase();
}
document.querySelectorAll(".co-tab-pane").forEach(function (pane) { document.querySelectorAll(".co-tab-pane").forEach(function (pane) {
pane.classList.toggle("active", pane.getAttribute("data-pane") === state.activeTab); pane.classList.toggle("active", pane.getAttribute("data-pane") === state.activeTab);
@@ -238,11 +246,14 @@
var listState = Object.assign({}, state, { servers: listServers }); var listState = Object.assign({}, state, { servers: listServers });
if (state.activeTab === "favorites" || state.activeTab === "recent") { if (state.activeTab === "favorites" || state.activeTab === "recent") {
listState.focusZone = state.focusZone === "details" || state.focusZone === "filters" ? "server-list" : state.focusZone; listState.focusZone = state.focusZone === "filters" ? "server-list" : state.focusZone;
} }
if (state.activeTab === "server-browser") { if (state.activeTab === "server-browser") {
CO_Filters.render(root, listState); CO_Filters.render(root, listState);
}
if (state.activeTab !== "direct-connect") {
CO_ServerDetails.render(root, listState); CO_ServerDetails.render(root, listState);
} }
@@ -366,20 +377,34 @@
joinServerLocal(sel.id); joinServerLocal(sel.id);
} }
function nextFocusZone(fromIdx, direction) {
var zones = FOCUS_ZONES.slice();
if (state.activeTab !== "server-browser") {
zones = zones.filter(function (z) { return z !== "filters"; });
}
var current = zones.indexOf(state.focusZone);
if (current < 0) current = fromIdx;
var next = current + direction;
if (next < 0 || next >= zones.length) return null;
return zones[next];
}
function navigateFocus(button) { function navigateFocus(button) {
var zoneIdx = FOCUS_ZONES.indexOf(state.focusZone);
var list = getActiveList(); var list = getActiveList();
if (button === "dpad_left") { if (button === "dpad_left") {
if (zoneIdx > 0) { var prevZone = nextFocusZone(FOCUS_ZONES.indexOf(state.focusZone), -1);
state.focusZone = FOCUS_ZONES[zoneIdx - 1]; if (prevZone) {
state.focusZone = prevZone;
render(); render();
} }
return; return;
} }
if (button === "dpad_right") { if (button === "dpad_right") {
if (zoneIdx < FOCUS_ZONES.length - 1 && state.activeTab === "server-browser") { if (state.activeTab === "direct-connect") return;
state.focusZone = FOCUS_ZONES[zoneIdx + 1]; var nextZone = nextFocusZone(FOCUS_ZONES.indexOf(state.focusZone), 1);
if (nextZone) {
state.focusZone = nextZone;
render(); render();
} }
return; return;
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Source artwork for inline borders in index.html (top + bottom strips) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1075.97 11" aria-hidden="true">
<polyline
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-miterlimit="10"
vector-effect="non-scaling-stroke"
points="0 10.97 0 0 1075.97 0 1075.97 10.5"
/>
<polyline
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-miterlimit="10"
vector-effect="non-scaling-stroke"
transform="translate(0, 0)"
points="0 0.47 0 8.91 1075.97 8.97 1075.97 0.47"
/>
</svg>

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -6,12 +6,13 @@
var pane = root.querySelector('[data-pane="direct-connect"]'); var pane = root.querySelector('[data-pane="direct-connect"]');
if (!pane) return; if (!pane) return;
var container = pane.querySelector(".co-direct-connect");
if (!container) return;
var address = state.directConnect.address || "127.0.0.1"; var address = state.directConnect.address || "127.0.0.1";
var port = state.directConnect.port || "7777"; var port = state.directConnect.port || "7777";
pane.innerHTML = container.innerHTML =
'<div class="co-direct-connect">' +
"<h2>Direct Connect</h2>" +
'<div class="co-direct-row">' + '<div class="co-direct-row">' +
'<label for="co-dc-address">Server Address</label>' + '<label for="co-dc-address">Server Address</label>' +
'<input class="co-filter-input" id="co-dc-address" type="text" value="' + escapeAttr(address) + '">' + '<input class="co-filter-input" id="co-dc-address" type="text" value="' + escapeAttr(address) + '">' +
@@ -21,8 +22,7 @@
'<input class="co-filter-input" id="co-dc-port" type="text" value="' + escapeAttr(port) + '">' + '<input class="co-filter-input" id="co-dc-port" type="text" value="' + escapeAttr(port) + '">' +
"</div>" + "</div>" +
'<button type="button" class="co-btn co-dc-connect">Connect</button>' + '<button type="button" class="co-btn co-dc-connect">Connect</button>' +
'<p class="co-detail-desc" style="margin-top:16px">Enter a server address to connect directly without browsing the list.</p>' + '<p class="co-detail-desc" style="margin-top:16px;color:var(--co-text-dim);font-size:var(--co-font-sm)">Enter a server address to connect directly without browsing the list.</p>';
"</div>";
}, },
bind: function (root, onConnect) { bind: function (root, onConnect) {
@@ -12,15 +12,7 @@
{ key: "modded", type: "checkbox", label: "Modded" } { key: "modded", type: "checkbox", label: "Modded" }
]; ];
window.CO_Filters = { function renderRow(row, index, state) {
FILTER_ROWS: FILTER_ROWS,
render: function (root, state) {
var el = root.querySelector(".co-filters");
if (!el) return;
var focusedClass = state.focusZone === "filters" ? " focused" : "";
var html = FILTER_ROWS.map(function (row, index) {
var rowFocused = state.focusZone === "filters" && state.selectedFilterIndex === index ? " focused" : ""; var rowFocused = state.focusZone === "filters" && state.selectedFilterIndex === index ? " focused" : "";
if (row.type === "text") { if (row.type === "text") {
return ( return (
@@ -44,16 +36,38 @@
} }
var checked = state.filters[row.key] ? " checked" : ""; var checked = state.filters[row.key] ? " checked" : "";
return ( return (
'<label class="co-checkbox-row co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' + '<label class="co-checkbox-row co-filter-row co-filter-checks' + rowFocused + '" data-filter-index="' + index + '">' +
'<span class="co-checkbox' + checked + '" data-filter="' + row.key + '"></span>' + '<span class="co-checkbox' + checked + '" data-filter="' + row.key + '"></span>' +
row.label + row.label +
"</label>" "</label>"
); );
}
window.CO_Filters = {
FILTER_ROWS: FILTER_ROWS,
render: function (root, state) {
var el = root.querySelector(".co-filters");
if (!el) return;
var focusedClass = state.focusZone === "filters" ? " focused" : "";
var searchHtml = renderRow(FILTER_ROWS[0], 0, state);
var selectHtml = FILTER_ROWS.slice(1, 5).map(function (row, i) {
return renderRow(row, i + 1, state);
}).join(""); }).join("");
var checkboxHtml = FILTER_ROWS.slice(5).map(function (row, i) {
return renderRow(row, i + 5, state);
}).join("");
var resetFocused = state.focusZone === "filters" && state.selectedFilterIndex === FILTER_ROWS.length ? " focused" : "";
html += '<button type="button" class="co-btn co-reset-btn">Reset Filters</button>';
el.className = "co-filters" + focusedClass; el.className = "co-filters" + focusedClass;
el.innerHTML = html; el.innerHTML =
'<div class="co-filters-row">' + searchHtml + "</div>" +
'<div class="co-filters-row co-filters-row-selects">' + selectHtml + "</div>" +
'<div class="co-filters-row co-filters-row-options">' +
checkboxHtml +
'<button type="button" class="co-btn co-reset-btn' + resetFocused + '">Reset Filters</button>' +
"</div>";
}, },
bind: function (root, onFilterChange, onReset) { bind: function (root, onFilterChange, onReset) {
@@ -3,50 +3,35 @@
window.CO_ServerDetails = { window.CO_ServerDetails = {
render: function (root, state) { render: function (root, state) {
var el = root.querySelector(".co-details"); var activePane = root.querySelector('.co-tab-pane[data-pane="' + state.activeTab + '"]');
var el = activePane && activePane.querySelector(".co-preview-area");
if (!el) return; if (!el) return;
el.className = "co-details" + (state.focusZone === "details" ? " focused" : "");
var server = state.servers[state.selectedServerIndex]; var server = state.servers[state.selectedServerIndex];
if (!server) { if (!server || !server.previewImage) {
el.innerHTML = '<div class="co-details-title">Server Details</div><div class="co-empty">Select a server</div>'; el.innerHTML = "";
el.classList.add("co-preview-hidden");
return; return;
} }
var rulesHtml = ""; el.classList.remove("co-preview-hidden");
if (server.rules && server.rules.length) {
rulesHtml = "<ul class=\"co-detail-rules\">" + server.rules.map(function (r) {
return "<li>" + escapeHtml(r) + "</li>";
}).join("") + "</ul>";
}
var previewHtml = server.previewImage var stats = [
? '<img src="' + escapeAttr(server.previewImage) + '" alt="Preview">' server.worldspace,
: "Preview Unavailable"; server.mode,
server.players + "/" + server.maxPlayers,
server.ping + " ms"
].filter(Boolean).join(" \u00b7 ");
el.innerHTML = el.innerHTML =
'<div class="co-details-title">Server Details</div>' + '<div class="co-preview"><img src="' + escapeAttr(server.previewImage) + '" alt="Preview"></div>' +
'<div class="co-preview">' + previewHtml + "</div>" + '<div class="co-preview-caption">' +
'<div class="co-detail-name">' + escapeHtml(server.name) + "</div>" + '<div class="co-preview-name">' + escapeHtml(server.name) + "</div>" +
detailRow("Region", server.region) + '<div class="co-preview-stats">' + escapeHtml(stats) + "</div>" +
detailRow("Players", server.players + "/" + server.maxPlayers) + "</div>";
detailRow("Ping", server.ping + " ms") +
detailRow("Worldspace", server.worldspace) +
detailRow("Mode", server.mode) +
detailRow("Status", server.status) +
detailRow("Modded", server.modded ? "Yes" : "No") +
detailRow("Password", server.passworded ? "Required" : "None") +
'<div class="co-detail-desc">' + escapeHtml(server.description || "") + "</div>" +
rulesHtml +
'<div class="co-detail-desc" style="margin-top:8px;color:var(--co-text-dim)">Mod list: coming soon</div>';
} }
}; };
function detailRow(label, value) {
return '<div class="co-detail-row"><span>' + label + '</span><span>' + escapeHtml(String(value)) + "</span></div>";
}
function escapeHtml(value) { function escapeHtml(value) {
return String(value) return String(value)
.replace(/&/g, "&amp;") .replace(/&/g, "&amp;")
@@ -17,6 +17,7 @@
if (!state.servers.length) { if (!state.servers.length) {
list.innerHTML = '<div class="co-empty">No servers found</div>'; list.innerHTML = '<div class="co-empty">No servers found</div>';
CO_ServerList.updateScrollIndicator(wrap, list);
return; return;
} }
@@ -26,16 +27,26 @@
var pingCls = CO_ServerList.pingClass(server.ping); var pingCls = CO_ServerList.pingClass(server.ping);
return ( return (
'<div class="co-server-row' + selected + '" data-index="' + index + '" data-id="' + escapeAttr(server.id) + '">' + '<div class="co-server-row' + selected + '" data-index="' + index + '" data-id="' + escapeAttr(server.id) + '">' +
'<div class="co-server-row-left">' +
'<span class="co-fav' + fav + '">' + (server.favorite ? "\u2605" : "\u2606") + "</span>" + '<span class="co-fav' + fav + '">' + (server.favorite ? "\u2605" : "\u2606") + "</span>" +
"<span>" + escapeHtml(server.name) + "</span>" + '<span class="co-server-row-name">' + escapeHtml(server.name) + "</span>" +
"</div>" +
'<div class="co-server-row-right">' +
"<span>" + server.players + "/" + server.maxPlayers + "</span>" + "<span>" + server.players + "/" + server.maxPlayers + "</span>" +
'<span class="co-ping ' + pingCls + '">' + server.ping + " ms</span>" + '<span class="co-ping ' + pingCls + '">' + server.ping + " ms</span>" +
"<span>" + escapeHtml(server.worldspace) + "</span>" + "</div>" +
"<span>" + escapeHtml(server.mode) + "</span>" +
"<span>" + escapeHtml(server.status) + "</span>" +
"</div>" "</div>"
); );
}).join(""); }).join("");
CO_ServerList.updateScrollIndicator(wrap, list);
},
updateScrollIndicator: function (wrap, list) {
var indicator = wrap && wrap.querySelector(".co-scroll-indicator");
if (!indicator || !list) return;
var hasOverflow = list.scrollHeight > list.clientHeight + 2;
indicator.classList.toggle("hidden", !hasOverflow);
}, },
bind: function (root, onSelect) { bind: function (root, onSelect) {
@@ -64,6 +75,10 @@
if (row) { if (row) {
row.scrollIntoView({ block: "nearest" }); row.scrollIntoView({ block: "nearest" });
} }
var wrap = root.querySelector(".co-server-list-wrap");
if (wrap && list) {
CO_ServerList.updateScrollIndicator(wrap, list);
}
} }
}; };
@@ -7,33 +7,35 @@
TAB_ORDER: TAB_ORDER, TAB_ORDER: TAB_ORDER,
render: function (root, state) { render: function (root, state) {
var tabsEl = root.querySelector(".co-tabs"); var navEl = root.querySelector(".co-nav-list");
if (!tabsEl) return; if (!navEl) return;
var labels = { var labels = {
"direct-connect": "Direct Connect", "direct-connect": "Direct Connect",
"server-browser": "Server Browser", "server-browser": "Browse",
favorites: "Favorites", favorites: "Favorites",
recent: "Recent" recent: "Recent"
}; };
tabsEl.innerHTML = TAB_ORDER.map(function (tabId, index) { navEl.innerHTML = TAB_ORDER.map(function (tabId, index) {
var active = state.activeTab === tabId ? " active" : ""; var active = state.activeTab === tabId ? " active" : "";
var focused = state.focusZone === "tabs" && state.selectedTabIndex === index ? " focused" : ""; var focused = state.focusZone === "tabs" && state.selectedTabIndex === index ? " focused" : "";
return ( return (
'<button type="button" class="co-tab' + active + focused + '" data-tab="' + tabId + '" data-index="' + index + '">' + '<button type="button" class="co-nav-item' + active + focused + '" data-tab="' + tabId + '" data-index="' + index + '">' +
labels[tabId] + labels[tabId] +
"</button>" "</button>"
); );
}).join(""); }).join("");
navEl.className = "co-nav-list" + (state.focusZone === "tabs" ? " focused" : "");
}, },
bind: function (root, onTabClick) { bind: function (root, onTabClick) {
var tabsEl = root.querySelector(".co-tabs"); var navEl = root.querySelector(".co-nav-list");
if (!tabsEl) return; if (!navEl) return;
tabsEl.addEventListener("click", function (e) { navEl.addEventListener("click", function (e) {
var btn = e.target.closest(".co-tab"); var btn = e.target.closest(".co-nav-item");
if (!btn) return; if (!btn) return;
onTabClick(btn.getAttribute("data-tab")); onTabClick(btn.getAttribute("data-tab"));
}); });
+37 -45
View File
@@ -9,63 +9,59 @@
<body> <body>
<div class="co-root" id="coRoot"> <div class="co-root" id="coRoot">
<div class="co-panel" id="coPanel"> <div class="co-panel" id="coPanel">
<header class="co-header"> <svg class="co-panel-border co-panel-border-top" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1075.97 11" preserveAspectRatio="none" aria-hidden="true">
<h1>Commonwealth Online</h1> <polyline class="co-panel-border-line" points="0 10.97 0 0 1075.97 0 1075.97 10.5"/>
<div class="co-subtitle">Server Browser</div> </svg>
</header> <svg class="co-panel-border co-panel-border-bottom" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1075.97 11" preserveAspectRatio="none" aria-hidden="true">
<polyline class="co-panel-border-line" points="0 0.47 0 8.91 1075.97 8.97 1075.97 0.47"/>
</svg>
<nav class="co-tabs"></nav> <div class="co-subpanel">
<nav class="co-nav-list"></nav>
<div class="co-body"> <div class="co-subpanel-content">
<div class="co-tab-content"> <div class="co-tab-content">
<div class="co-tab-pane" data-pane="direct-connect"></div> <div class="co-tab-pane" data-pane="direct-connect">
<header class="co-subpanel-header">
<h2 class="co-subpanel-title">Direct Connect</h2>
</header>
<div class="co-direct-connect"></div>
</div>
<div class="co-tab-pane active" data-pane="server-browser"> <div class="co-tab-pane active" data-pane="server-browser">
<div class="co-browser-layout"> <header class="co-subpanel-header">
<aside class="co-filters"></aside> <h2 class="co-subpanel-title" id="coSubpanelTitle">Servers</h2>
<div class="co-server-list-wrap"> <div class="co-subpanel-meta">
<div class="co-server-list-header"> <span id="coConnectionStatus">Online</span>
<span></span> <span class="co-count" id="coServerCount"></span>
<span>Server Name</span>
<span>Players</span>
<span>Ping</span>
<span>Worldspace</span>
<span>Mode</span>
<span>Status</span>
</div> </div>
</header>
<div class="co-filters"></div>
<div class="co-server-list-wrap">
<div class="co-server-list"></div> <div class="co-server-list"></div>
</div> <div class="co-scroll-indicator" aria-hidden="true">&raquo;&raquo;</div>
<aside class="co-details"></aside>
</div> </div>
</div> </div>
<div class="co-tab-pane" data-pane="favorites"> <div class="co-tab-pane" data-pane="favorites">
<div class="co-browser-layout co-favorites-layout"> <header class="co-subpanel-header">
<h2 class="co-subpanel-title">Favorites</h2>
</header>
<div class="co-preview-area"></div>
<div class="co-server-list-wrap"> <div class="co-server-list-wrap">
<div class="co-server-list-header">
<span></span>
<span>Server Name</span>
<span>Players</span>
<span>Ping</span>
<span>Worldspace</span>
<span>Mode</span>
<span>Status</span>
</div>
<div class="co-server-list co-favorites-list"></div> <div class="co-server-list co-favorites-list"></div>
<div class="co-scroll-indicator" aria-hidden="true">&raquo;&raquo;</div>
</div> </div>
</div> </div>
</div>
<div class="co-tab-pane" data-pane="recent"> <div class="co-tab-pane" data-pane="recent">
<div class="co-browser-layout co-recent-layout"> <header class="co-subpanel-header">
<h2 class="co-subpanel-title">Recent</h2>
</header>
<div class="co-preview-area"></div>
<div class="co-server-list-wrap"> <div class="co-server-list-wrap">
<div class="co-server-list-header">
<span></span>
<span>Server Name</span>
<span>Players</span>
<span>Ping</span>
<span>Worldspace</span>
<span>Mode</span>
<span>Status</span>
</div>
<div class="co-server-list co-recent-list"></div> <div class="co-server-list co-recent-list"></div>
<div class="co-scroll-indicator" aria-hidden="true">&raquo;&raquo;</div>
</div> </div>
</div> </div>
</div> </div>
@@ -73,10 +69,6 @@
</div> </div>
<footer class="co-action-bar"> <footer class="co-action-bar">
<div class="co-status-text">
Status: <span id="coConnectionStatus">Online</span>
<span class="co-count" id="coServerCount"></span>
</div>
<div class="co-prompts"></div> <div class="co-prompts"></div>
</footer> </footer>
+360 -365
View File
@@ -1,36 +1,47 @@
@font-face {
font-family: "Roboto Condensed";
src: url("./assets/fonts/RobotoCondensed-SemiBold.ttf") format("truetype");
font-weight: 600 700;
font-style: normal;
font-display: block;
}
:root { :root {
--co-green: #4dff3a; --co-green: #2dff28;
--co-green-soft: rgba(77, 255, 58, 0.88); --co-green-soft: #2dff28;
--co-green-dim: rgba(57, 255, 34, 0.35); --co-green-dim: rgba(45, 255, 40, 0.22);
--co-bg: rgba(0, 14, 0, 0.92); --co-bg: rgba(0, 12, 0, 0.62);
--co-bg-dark: rgba(0, 8, 0, 0.96); --co-bg-dark: rgba(0, 10, 0, 0.72);
--co-bg-list: rgba(0, 12, 0, 0.88); --co-bg-list: transparent;
--co-selected: rgba(77, 255, 58, 0.92); --co-selected: #2dff28;
--co-selected-text: #041200; --co-selected-text: #001400;
--co-border: rgba(77, 255, 58, 0.9); --co-border: #2dff28;
--co-text-dim: rgba(77, 255, 58, 0.68); --co-text-dim: rgba(45, 255, 40, 0.5);
--co-ping-good: #4dff3a; --co-ping-good: #2dff28;
--co-ping-mid: #c8ff3a; --co-ping-mid: #c8ff28;
--co-ping-bad: #ffd43a; --co-ping-bad: #ffd428;
--co-danger: #ff6a6a; --co-danger: #ff6a6a;
--co-font: "Consolas", "Courier New", monospace; --co-font: "Roboto Condensed", "Bahnschrift", "Arial Narrow", Arial, sans-serif;
--co-spacing: 10px; --co-transition: 80ms linear;
--co-transition: 120ms ease; --co-border-stroke: 5px;
--co-text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85); --co-border-strip-height: 11px;
--co-border-cap: 10px;
--co-border-edge: 4px;
/* /*
* Fallout 4 main-menu safe zones (1280×720 UI scaled to viewport): * Fallout 4 submenu alignment (1280×720, like ADD-ONS panel):
* Left — menu list + green selection bar (~0–46%) * Left — tight gap, no overlap (~454px)
* Top — "Fallout 4 Updates" / installed-content panel (~0–26% height) * Top — MULTIPLAYER row (~286px)
* Bottom — Bethesda logo / controller hints (~0–8% height) * Bottom — main menu box bottom (~146px inset)
*/ */
--co-panel-left: max(44vw, 500px); --co-panel-left: max(35.6vw, 454px);
--co-panel-right: max(2vw, 24px); --co-panel-right: max(2vw, 24px);
--co-panel-top: max(26vh, 220px); --co-panel-top: max(39.5vh, 286px);
--co-panel-bottom: max(8vh, 72px); --co-panel-bottom: max(20.5vh, 146px);
--co-font-sm: clamp(11px, 0.95vw, 13px); --co-font-xs: clamp(9px, 0.75vw, 11px);
--co-font-md: clamp(13px, 1.1vw, 16px); --co-font-sm: clamp(10px, 0.85vw, 12px);
--co-font-lg: clamp(15px, 1.25vw, 19px); --co-font-md: clamp(13px, 1.05vw, 16px);
--co-font-xl: clamp(18px, 1.5vw, 24px); --co-font-lg: clamp(16px, 1.3vw, 20px);
--co-nav-width: clamp(152px, 14vw, 196px);
} }
* { * {
@@ -45,9 +56,11 @@ body {
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
font-family: var(--co-font); font-family: var(--co-font);
font-weight: 600;
color: var(--co-green); color: var(--co-green);
background: transparent; background: transparent;
user-select: none; user-select: none;
-webkit-font-smoothing: antialiased;
} }
.co-root { .co-root {
@@ -58,151 +71,271 @@ body {
background: transparent; background: transparent;
} }
/* Panel is inset from all Fallout main-menu chrome so vanilla UI stays visible. */ /* Submenu panel — SVG border overlay + translucent fill. */
.co-panel { .co-panel {
position: absolute; position: absolute;
left: var(--co-panel-left); left: var(--co-panel-left);
right: var(--co-panel-right); right: var(--co-panel-right);
top: var(--co-panel-top); top: var(--co-panel-top);
bottom: var(--co-panel-bottom); bottom: var(--co-panel-bottom);
width: auto; min-height: 0;
height: auto;
min-width: 0;
min-height: 360px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: 2px solid var(--co-border); border: none;
background: var(--co-bg); background: var(--co-bg);
box-shadow:
0 0 0 1px rgba(77, 255, 58, 0.12),
0 0 48px rgba(0, 0, 0, 0.55),
inset 0 0 80px rgba(0, 0, 0, 0.25);
pointer-events: auto; pointer-events: auto;
transition: opacity var(--co-transition); transition: opacity var(--co-transition);
backdrop-filter: blur(2px); padding: var(--co-border-cap) var(--co-border-edge);
overflow: visible;
} }
.co-panel.co-loading { .co-panel.co-loading {
opacity: 0.92; opacity: 0.85;
} }
.co-header { /*
padding: 18px 22px 14px; * Top/bottom border strips (from assets/MenuBoarder.svg).
border-bottom: 1px solid var(--co-green-dim); * Split so each edge anchors correctly — a single stretched SVG misaligns in Ultralight.
background: rgba(0, 6, 0, 0.45); */
.co-panel-border {
position: absolute;
left: 0;
width: 100%;
height: var(--co-border-strip-height);
pointer-events: none;
z-index: 10;
} }
.co-header h1 { .co-panel-border-top {
font-size: var(--co-font-xl); top: 0;
letter-spacing: 0.18em;
text-transform: uppercase;
font-weight: 700;
text-shadow: var(--co-text-shadow), 0 0 12px rgba(77, 255, 58, 0.2);
} }
.co-header .co-subtitle { .co-panel-border-bottom {
margin-top: 6px; bottom: 0;
font-size: var(--co-font-sm);
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--co-text-dim);
} }
.co-tabs { .co-panel-border-line {
fill: none;
stroke: var(--co-border);
stroke-width: var(--co-border-stroke);
stroke-miterlimit: 10;
vector-effect: non-scaling-stroke;
}
.co-subpanel {
flex: 1;
min-height: 0;
display: flex; display: flex;
gap: 4px; overflow: hidden;
border-bottom: 1px solid var(--co-green-dim);
padding: 0 16px;
background: rgba(0, 6, 0, 0.3);
} }
.co-tab { /* Left nav — mirrors Settings / Load category list. */
.co-nav-list {
flex: 0 0 var(--co-nav-width);
display: flex;
flex-direction: column;
padding: 6px 0;
border-right: 1px solid var(--co-green-dim);
overflow-y: auto;
}
.co-nav-item {
appearance: none; appearance: none;
border: none; border: none;
background: transparent; background: transparent;
color: var(--co-text-dim);
font: inherit;
font-size: var(--co-font-sm);
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 12px 18px;
cursor: pointer;
border: 1px solid transparent;
border-bottom: none;
margin-bottom: -1px;
transition: color var(--co-transition), border-color var(--co-transition);
}
.co-tab:hover {
color: var(--co-green-soft); color: var(--co-green-soft);
font: inherit;
font-size: var(--co-font-md);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
text-align: left;
padding: 10px 16px;
cursor: pointer;
transition: background var(--co-transition), color var(--co-transition);
} }
.co-tab.active { .co-nav-item:hover:not(.active) {
color: var(--co-green); background: rgba(45, 255, 40, 0.08);
border-color: var(--co-border);
background: rgba(57, 255, 34, 0.06);
} }
.co-tab.focused { .co-nav-item.active {
outline: 1px solid var(--co-green); background: var(--co-selected);
outline-offset: -2px; color: var(--co-selected-text);
} }
.co-body { .co-nav-item.focused:not(.active) {
background: rgba(45, 255, 40, 0.12);
}
.co-nav-list.focused .co-nav-item.focused:not(.active) {
background: rgba(45, 255, 40, 0.12);
}
/* Right content */
.co-subpanel-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.co-tab-content {
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow: hidden; overflow: hidden;
} }
.co-browser-layout { .co-tab-pane {
display: grid; display: none;
grid-template-columns: 1fr minmax(240px, 30%); flex-direction: column;
grid-template-rows: auto 1fr;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
} }
.co-filters { .co-tab-pane.active {
grid-column: 1 / -1;
border-bottom: 1px solid var(--co-green-dim);
padding: 12px 16px;
display: flex; display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-end;
gap: 10px 14px;
overflow: visible;
background: rgba(0, 8, 0, 0.55);
} }
.co-filters.focused { .co-subpanel-header {
flex-shrink: 0;
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 16px;
padding: 10px 16px 8px;
}
.co-subpanel-title {
font-size: var(--co-font-sm);
font-weight: 700;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--co-text-dim);
}
.co-subpanel-meta {
display: flex;
gap: 10px;
font-size: var(--co-font-xs);
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--co-text-dim);
white-space: nowrap;
}
.co-subpanel-meta .co-count {
color: var(--co-green-soft);
}
/* Preview — Load-menu thumbnail (favorites / recent only). */
.co-preview-area {
flex-shrink: 0;
margin: 0 12px 8px;
border: 1px solid var(--co-green-dim);
background: var(--co-bg-dark);
overflow: hidden;
}
.co-preview-area:empty,
.co-preview-area.co-preview-hidden {
display: none;
}
.co-preview {
width: 100%;
height: clamp(72px, 14vh, 120px);
display: flex;
align-items: center;
justify-content: center;
color: var(--co-text-dim);
font-size: var(--co-font-sm);
letter-spacing: 0.1em;
text-transform: uppercase;
overflow: hidden;
}
.co-preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.co-preview-caption {
padding: 6px 10px;
border-top: 1px solid var(--co-green-dim);
font-size: var(--co-font-xs);
letter-spacing: 0.08em;
text-transform: uppercase;
line-height: 1.35;
}
.co-preview-caption .co-preview-name {
font-size: var(--co-font-sm);
font-weight: 700;
color: var(--co-green);
}
.co-preview-caption .co-preview-stats {
color: var(--co-text-dim);
}
/* Filters */
.co-filters {
flex-shrink: 0;
padding: 0 12px 10px;
display: flex;
flex-direction: column;
gap: 6px;
}
.co-filters.focused .co-filter-row.focused .co-filter-input,
.co-filters.focused .co-filter-row.focused .co-filter-select,
.co-filters.focused .co-filter-row.focused .co-checkbox {
outline: 1px solid var(--co-green); outline: 1px solid var(--co-green);
outline-offset: -2px; outline-offset: 0;
}
.co-filters-row {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 6px 10px;
}
.co-filters-row-selects .co-filter-row {
flex: 1 1 90px;
min-width: 80px;
}
.co-filters-row-options {
align-items: center;
} }
.co-filter-row { .co-filter-row {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 4px; gap: 3px;
min-width: 0; min-width: 0;
} }
.co-filter-row[data-filter-index="0"] { .co-filter-row[data-filter-index="0"] {
flex: 1 1 180px; width: 100%;
min-width: 160px;
} }
.co-filter-row[data-filter-index="1"], .co-filter-row.co-filter-checks {
.co-filter-row[data-filter-index="2"], flex-direction: row;
.co-filter-row[data-filter-index="3"], align-items: center;
.co-filter-row[data-filter-index="4"] { gap: 5px;
flex: 0 1 130px; flex: 0 0 auto;
min-width: 110px;
} }
.co-filter-label { .co-filter-label {
font-size: var(--co-font-sm); font-size: var(--co-font-xs);
letter-spacing: 0.1em; font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase; text-transform: uppercase;
color: var(--co-text-dim); color: var(--co-text-dim);
} }
@@ -215,8 +348,13 @@ body {
color: var(--co-green); color: var(--co-green);
font: inherit; font: inherit;
font-size: var(--co-font-sm); font-size: var(--co-font-sm);
padding: 8px 10px; font-weight: 600;
text-shadow: var(--co-text-shadow); padding: 5px 8px;
border-radius: 0;
}
.co-filter-input::placeholder {
color: var(--co-text-dim);
} }
.co-filter-input:focus, .co-filter-input:focus,
@@ -225,30 +363,22 @@ body {
outline: none; outline: none;
} }
.co-filter-row.focused .co-filter-input,
.co-filter-row.focused .co-filter-select,
.co-filter-row.focused .co-checkbox {
outline: 1px solid var(--co-green);
}
.co-checkbox-row { .co-checkbox-row {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 5px;
font-size: var(--co-font-sm); font-size: var(--co-font-xs);
letter-spacing: 0.05em; font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase; text-transform: uppercase;
cursor: pointer; cursor: pointer;
padding: 8px 0 0;
flex: 0 0 auto;
align-self: center;
} }
.co-checkbox { .co-checkbox {
width: 16px; width: 13px;
height: 16px; height: 13px;
border: 1px solid var(--co-border); border: 1px solid var(--co-border);
background: var(--co-bg-dark); background: transparent;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -257,109 +387,81 @@ body {
.co-checkbox.checked::after { .co-checkbox.checked::after {
content: "X"; content: "X";
font-size: 11px; font-size: 9px;
font-weight: 700;
color: var(--co-green); color: var(--co-green);
line-height: 1;
} }
.co-btn { .co-btn {
appearance: none; appearance: none;
border: 1px solid var(--co-border); border: 1px solid var(--co-green-dim);
background: rgba(77, 255, 58, 0.1); background: transparent;
color: var(--co-green); color: var(--co-green);
font: inherit; font: inherit;
font-size: var(--co-font-sm); font-size: var(--co-font-xs);
letter-spacing: 0.08em; font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
padding: 10px 14px; padding: 5px 10px;
cursor: pointer; cursor: pointer;
margin-left: auto; margin-left: auto;
align-self: flex-end;
flex-shrink: 0; flex-shrink: 0;
transition: background var(--co-transition); transition: background var(--co-transition);
} }
.co-btn:hover { .co-btn:hover {
background: rgba(57, 255, 34, 0.18); background: rgba(45, 255, 40, 0.1);
} }
.co-btn.focused { .co-btn.focused {
background: rgba(45, 255, 40, 0.18);
outline: 1px solid var(--co-green); outline: 1px solid var(--co-green);
background: rgba(57, 255, 34, 0.22);
} }
/* Server list — save-file style rows */
.co-server-list-wrap { .co-server-list-wrap {
flex: 1;
min-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 0; position: relative;
border-right: 1px solid var(--co-green-dim);
background: var(--co-bg-list);
} }
.co-server-list-wrap.focused { .co-server-list-wrap.focused {
outline: 1px solid var(--co-green); background: rgba(45, 255, 40, 0.03);
outline-offset: -2px;
}
.co-server-list-header {
display: grid;
grid-template-columns: 32px 2.2fr 72px 64px 1fr 88px 72px;
gap: 8px;
padding: 12px 16px;
font-size: var(--co-font-sm);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--co-text-dim);
border-bottom: 1px solid var(--co-green-dim);
background: rgba(0, 6, 0, 0.5);
} }
.co-server-list { .co-server-list {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--co-green-dim) var(--co-bg-dark); scrollbar-color: var(--co-green-dim) transparent;
padding: 0 4px;
} }
.co-server-list::-webkit-scrollbar { .co-server-list::-webkit-scrollbar {
width: 10px; width: 4px;
}
.co-server-list::-webkit-scrollbar-track {
background: var(--co-bg-dark);
} }
.co-server-list::-webkit-scrollbar-thumb { .co-server-list::-webkit-scrollbar-thumb {
background: var(--co-green-dim); background: var(--co-green-dim);
border: 1px solid var(--co-border);
} }
.co-server-row { .co-server-row {
display: grid; display: flex;
grid-template-columns: 32px 2.2fr 72px 64px 1fr 88px 72px;
gap: 8px;
padding: 12px 16px;
font-size: var(--co-font-md);
line-height: 1.35;
align-items: center; align-items: center;
cursor: pointer; justify-content: space-between;
border-bottom: 1px solid rgba(77, 255, 58, 0.1); gap: 16px;
transition: background var(--co-transition); padding: 8px 12px;
text-shadow: var(--co-text-shadow);
}
.co-server-row > span:nth-child(2) {
font-size: var(--co-font-lg);
font-weight: 700;
letter-spacing: 0.04em;
}
.co-server-row > span:nth-child(3) {
font-size: var(--co-font-md); font-size: var(--co-font-md);
font-weight: 600; line-height: 1.2;
cursor: pointer;
transition: background var(--co-transition);
} }
.co-server-row:hover { .co-server-row:hover:not(.selected) {
background: rgba(57, 255, 34, 0.06); background: rgba(45, 255, 40, 0.06);
} }
.co-server-row.selected { .co-server-row.selected {
@@ -367,15 +469,44 @@ body {
color: var(--co-selected-text); color: var(--co-selected-text);
} }
.co-server-row.selected .co-ping, .co-server-row-left {
.co-server-row.selected .co-fav { display: flex;
align-items: center;
gap: 10px;
min-width: 0;
flex: 1;
}
.co-server-row-name {
font-size: var(--co-font-lg);
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.co-server-row-right {
display: flex;
align-items: center;
gap: 20px;
flex-shrink: 0;
font-size: var(--co-font-md);
font-weight: 700;
letter-spacing: 0.04em;
}
.co-server-row.selected .co-server-row-right,
.co-server-row.selected .co-fav,
.co-server-row.selected .co-ping {
color: var(--co-selected-text); color: var(--co-selected-text);
} }
.co-fav { .co-fav {
text-align: center;
color: var(--co-text-dim); color: var(--co-text-dim);
font-size: 16px; font-size: 15px;
flex-shrink: 0;
} }
.co-fav.on { .co-fav.on {
@@ -396,120 +527,32 @@ body {
color: var(--co-selected-text); color: var(--co-selected-text);
} }
.co-details { .co-scroll-indicator {
padding: 14px 16px;
overflow-y: auto;
font-size: var(--co-font-md);
background: rgba(0, 8, 0, 0.45);
line-height: 1.4;
}
.co-details.focused {
outline: 1px solid var(--co-green);
outline-offset: -2px;
}
.co-details-title {
font-size: var(--co-font-sm);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--co-text-dim);
margin-bottom: 10px;
}
.co-preview {
width: 100%;
height: 96px;
border: 1px solid var(--co-green-dim);
background: var(--co-bg-dark);
margin-bottom: 12px;
display: flex;
align-items: center;
justify-content: center;
color: var(--co-text-dim);
font-size: var(--co-font-sm);
letter-spacing: 0.06em;
text-transform: uppercase;
overflow: hidden;
}
.co-preview img {
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.85;
}
.co-detail-name {
font-size: var(--co-font-lg);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 10px;
font-weight: 700;
text-shadow: var(--co-text-shadow);
}
.co-detail-row {
display: flex;
justify-content: space-between;
gap: 12px;
padding: 6px 0;
border-bottom: 1px solid rgba(77, 255, 58, 0.08);
}
.co-detail-row span:first-child {
color: var(--co-text-dim);
text-transform: uppercase;
font-size: var(--co-font-sm);
letter-spacing: 0.05em;
flex-shrink: 0; flex-shrink: 0;
} text-align: center;
padding: 2px 0 4px;
.co-detail-row span:last-child { font-size: var(--co-font-xs);
text-align: right; letter-spacing: 0.15em;
font-weight: 600;
}
.co-detail-desc {
margin-top: 12px;
line-height: 1.5;
color: var(--co-green-soft);
font-size: var(--co-font-sm);
}
.co-detail-rules {
margin-top: 10px;
padding-left: 16px;
color: var(--co-text-dim); color: var(--co-text-dim);
font-size: var(--co-font-sm);
} }
.co-detail-rules li { .co-scroll-indicator.hidden {
margin-bottom: 3px; display: none;
} }
/* Footer prompts */
.co-action-bar { .co-action-bar {
flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: flex-end;
padding: 12px 18px; padding: 6px 14px 8px;
border-top: 1px solid var(--co-green-dim); border-top: 1px solid var(--co-green-dim);
font-size: var(--co-font-sm);
letter-spacing: 0.05em;
background: rgba(0, 6, 0, 0.45);
}
.co-status-text {
text-transform: uppercase;
}
.co-status-text .co-count {
color: var(--co-text-dim);
} }
.co-prompts { .co-prompts {
display: flex; display: flex;
gap: 20px; gap: 16px;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: flex-end; justify-content: flex-end;
} }
@@ -517,62 +560,59 @@ body {
.co-prompt { .co-prompt {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 6px;
font-size: var(--co-font-sm); font-size: var(--co-font-xs);
letter-spacing: 0.06em; font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
color: var(--co-green-soft);
} }
.co-prompt-key { .co-prompt-key {
width: 22px; min-width: 18px;
height: 22px; height: 18px;
border: 1px solid var(--co-border); border: 1px solid var(--co-border);
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 10px; font-size: 8px;
font-weight: 700; font-weight: 700;
padding: 0 3px;
} }
.co-direct-connect { .co-direct-connect {
padding: 28px 36px; padding: 16px 20px;
max-width: 520px; max-width: 480px;
}
.co-direct-connect h2 {
font-size: var(--co-font-lg);
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 18px;
} }
.co-direct-row { .co-direct-row {
margin-bottom: 14px; margin-bottom: 12px;
} }
.co-direct-row label { .co-direct-row label {
display: block; display: block;
font-size: var(--co-font-sm); font-size: var(--co-font-xs);
letter-spacing: 0.08em; font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase; text-transform: uppercase;
color: var(--co-text-dim); color: var(--co-text-dim);
margin-bottom: 6px; margin-bottom: 4px;
} }
.co-empty { .co-empty {
padding: 32px; padding: 24px 16px;
text-align: center; text-align: center;
color: var(--co-text-dim); color: var(--co-text-dim);
font-size: var(--co-font-md); font-size: var(--co-font-md);
letter-spacing: 0.06em; letter-spacing: 0.08em;
text-transform: uppercase; text-transform: uppercase;
} }
.co-join-overlay { .co-join-overlay {
position: absolute; position: absolute;
inset: 0; inset: 0;
background: rgba(0, 10, 0, 0.88); background: rgba(0, 0, 0, 0.75);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
@@ -597,10 +637,11 @@ body {
} }
.co-spinner { .co-spinner {
width: 24px; width: 22px;
height: 24px; height: 22px;
border: 2px solid var(--co-green-dim); border: 2px solid var(--co-green-dim);
border-top-color: var(--co-green); border-top-color: var(--co-green);
border-radius: 50%;
animation: co-spin 0.8s linear infinite; animation: co-spin 0.8s linear infinite;
} }
@@ -608,68 +649,22 @@ body {
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
.co-tab-content {
height: 100%;
overflow: hidden;
}
.co-tab-pane {
display: none;
height: 100%;
}
.co-tab-pane.active {
display: block;
}
.co-filters::-webkit-scrollbar,
.co-details::-webkit-scrollbar {
width: 8px;
}
.co-filters::-webkit-scrollbar-thumb,
.co-details::-webkit-scrollbar-thumb {
background: var(--co-green-dim);
}
.co-favorites-layout,
.co-recent-layout {
grid-template-columns: 1fr;
grid-template-rows: 1fr;
}
.co-favorites-layout .co-server-list-wrap,
.co-recent-layout .co-server-list-wrap {
grid-column: 1;
border-right: none;
}
@media (max-width: 1280px) { @media (max-width: 1280px) {
:root { :root {
--co-panel-left: max(46vw, 520px); --co-panel-left: max(35.8vw, 450px);
--co-panel-right: max(2vw, 16px); --co-panel-right: max(2vw, 16px);
--co-panel-top: max(28vh, 200px); --co-panel-top: max(40vh, 280px);
--co-panel-bottom: max(9vh, 64px); --co-panel-bottom: max(21vh, 142px);
--co-nav-width: clamp(136px, 15vw, 168px);
} }
.co-browser-layout {
grid-template-columns: 1fr minmax(200px, 34%);
}
.co-filter-row[data-filter-index="1"],
.co-filter-row[data-filter-index="2"],
.co-filter-row[data-filter-index="3"],
.co-filter-row[data-filter-index="4"] {
flex: 0 1 100px;
min-width: 90px;
}
} }
@media (min-width: 2560px) { @media (min-width: 2560px) {
:root { :root {
--co-panel-left: max(44vw, 960px); --co-panel-left: max(35.6vw, 908px);
--co-panel-right: max(2.5vw, 48px); --co-panel-right: max(2.5vw, 48px);
--co-panel-top: max(24vh, 280px); --co-panel-top: max(39.5vh, 572px);
--co-panel-bottom: max(7vh, 96px); --co-panel-bottom: max(20.5vh, 292px);
} }
} }