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:
@@ -6,7 +6,7 @@
|
||||
var joinOverlay = document.getElementById("coJoinOverlay");
|
||||
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_RATE_MS = 80;
|
||||
|
||||
@@ -144,9 +144,14 @@
|
||||
}
|
||||
|
||||
function updateServerCount() {
|
||||
var el = document.getElementById("coServerCount");
|
||||
if (el) {
|
||||
el.textContent = " \u2014 " + state.servers.length + " servers found";
|
||||
var countEl = document.getElementById("coServerCount");
|
||||
var titleEl = document.getElementById("coSubpanelTitle");
|
||||
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_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) {
|
||||
pane.classList.toggle("active", pane.getAttribute("data-pane") === state.activeTab);
|
||||
@@ -238,11 +246,14 @@
|
||||
|
||||
var listState = Object.assign({}, state, { servers: listServers });
|
||||
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") {
|
||||
CO_Filters.render(root, listState);
|
||||
}
|
||||
|
||||
if (state.activeTab !== "direct-connect") {
|
||||
CO_ServerDetails.render(root, listState);
|
||||
}
|
||||
|
||||
@@ -366,20 +377,34 @@
|
||||
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) {
|
||||
var zoneIdx = FOCUS_ZONES.indexOf(state.focusZone);
|
||||
var list = getActiveList();
|
||||
|
||||
if (button === "dpad_left") {
|
||||
if (zoneIdx > 0) {
|
||||
state.focusZone = FOCUS_ZONES[zoneIdx - 1];
|
||||
var prevZone = nextFocusZone(FOCUS_ZONES.indexOf(state.focusZone), -1);
|
||||
if (prevZone) {
|
||||
state.focusZone = prevZone;
|
||||
render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (button === "dpad_right") {
|
||||
if (zoneIdx < FOCUS_ZONES.length - 1 && state.activeTab === "server-browser") {
|
||||
state.focusZone = FOCUS_ZONES[zoneIdx + 1];
|
||||
if (state.activeTab === "direct-connect") return;
|
||||
var nextZone = nextFocusZone(FOCUS_ZONES.indexOf(state.focusZone), 1);
|
||||
if (nextZone) {
|
||||
state.focusZone = nextZone;
|
||||
render();
|
||||
}
|
||||
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.
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -6,12 +6,13 @@
|
||||
var pane = root.querySelector('[data-pane="direct-connect"]');
|
||||
if (!pane) return;
|
||||
|
||||
var container = pane.querySelector(".co-direct-connect");
|
||||
if (!container) return;
|
||||
|
||||
var address = state.directConnect.address || "127.0.0.1";
|
||||
var port = state.directConnect.port || "7777";
|
||||
|
||||
pane.innerHTML =
|
||||
'<div class="co-direct-connect">' +
|
||||
"<h2>Direct Connect</h2>" +
|
||||
container.innerHTML =
|
||||
'<div class="co-direct-row">' +
|
||||
'<label for="co-dc-address">Server Address</label>' +
|
||||
'<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) + '">' +
|
||||
"</div>" +
|
||||
'<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>' +
|
||||
"</div>";
|
||||
'<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>';
|
||||
},
|
||||
|
||||
bind: function (root, onConnect) {
|
||||
|
||||
@@ -12,6 +12,37 @@
|
||||
{ key: "modded", type: "checkbox", label: "Modded" }
|
||||
];
|
||||
|
||||
function renderRow(row, index, state) {
|
||||
var rowFocused = state.focusZone === "filters" && state.selectedFilterIndex === index ? " focused" : "";
|
||||
if (row.type === "text") {
|
||||
return (
|
||||
'<div class="co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' +
|
||||
'<div class="co-filter-label">' + row.label + '</div>' +
|
||||
'<input class="co-filter-input" type="text" data-filter="' + row.key + '" placeholder="' + row.placeholder + '" value="' + escapeAttr(state.filters[row.key] || "") + '">' +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
if (row.type === "select") {
|
||||
var opts = row.options.map(function (opt) {
|
||||
var sel = (state.filters[row.key] || row.options[0]) === opt ? " selected" : "";
|
||||
return '<option value="' + escapeAttr(opt) + '"' + sel + ">" + opt + "</option>";
|
||||
}).join("");
|
||||
return (
|
||||
'<div class="co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' +
|
||||
'<div class="co-filter-label">' + row.label + '</div>' +
|
||||
'<select class="co-filter-select" data-filter="' + row.key + '">' + opts + "</select>" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
var checked = state.filters[row.key] ? " checked" : "";
|
||||
return (
|
||||
'<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>' +
|
||||
row.label +
|
||||
"</label>"
|
||||
);
|
||||
}
|
||||
|
||||
window.CO_Filters = {
|
||||
FILTER_ROWS: FILTER_ROWS,
|
||||
|
||||
@@ -20,40 +51,23 @@
|
||||
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" : "";
|
||||
if (row.type === "text") {
|
||||
return (
|
||||
'<div class="co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' +
|
||||
'<div class="co-filter-label">' + row.label + '</div>' +
|
||||
'<input class="co-filter-input" type="text" data-filter="' + row.key + '" placeholder="' + row.placeholder + '" value="' + escapeAttr(state.filters[row.key] || "") + '">' +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
if (row.type === "select") {
|
||||
var opts = row.options.map(function (opt) {
|
||||
var sel = (state.filters[row.key] || row.options[0]) === opt ? " selected" : "";
|
||||
return '<option value="' + escapeAttr(opt) + '"' + sel + ">" + opt + "</option>";
|
||||
}).join("");
|
||||
return (
|
||||
'<div class="co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' +
|
||||
'<div class="co-filter-label">' + row.label + '</div>' +
|
||||
'<select class="co-filter-select" data-filter="' + row.key + '">' + opts + "</select>" +
|
||||
"</div>"
|
||||
);
|
||||
}
|
||||
var checked = state.filters[row.key] ? " checked" : "";
|
||||
return (
|
||||
'<label class="co-checkbox-row co-filter-row' + rowFocused + '" data-filter-index="' + index + '">' +
|
||||
'<span class="co-checkbox' + checked + '" data-filter="' + row.key + '"></span>' +
|
||||
row.label +
|
||||
"</label>"
|
||||
);
|
||||
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("");
|
||||
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.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) {
|
||||
|
||||
@@ -3,50 +3,35 @@
|
||||
|
||||
window.CO_ServerDetails = {
|
||||
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;
|
||||
|
||||
el.className = "co-details" + (state.focusZone === "details" ? " focused" : "");
|
||||
|
||||
var server = state.servers[state.selectedServerIndex];
|
||||
if (!server) {
|
||||
el.innerHTML = '<div class="co-details-title">Server Details</div><div class="co-empty">Select a server</div>';
|
||||
if (!server || !server.previewImage) {
|
||||
el.innerHTML = "";
|
||||
el.classList.add("co-preview-hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
var rulesHtml = "";
|
||||
if (server.rules && server.rules.length) {
|
||||
rulesHtml = "<ul class=\"co-detail-rules\">" + server.rules.map(function (r) {
|
||||
return "<li>" + escapeHtml(r) + "</li>";
|
||||
}).join("") + "</ul>";
|
||||
}
|
||||
el.classList.remove("co-preview-hidden");
|
||||
|
||||
var previewHtml = server.previewImage
|
||||
? '<img src="' + escapeAttr(server.previewImage) + '" alt="Preview">'
|
||||
: "Preview Unavailable";
|
||||
var stats = [
|
||||
server.worldspace,
|
||||
server.mode,
|
||||
server.players + "/" + server.maxPlayers,
|
||||
server.ping + " ms"
|
||||
].filter(Boolean).join(" \u00b7 ");
|
||||
|
||||
el.innerHTML =
|
||||
'<div class="co-details-title">Server Details</div>' +
|
||||
'<div class="co-preview">' + previewHtml + "</div>" +
|
||||
'<div class="co-detail-name">' + escapeHtml(server.name) + "</div>" +
|
||||
detailRow("Region", server.region) +
|
||||
detailRow("Players", server.players + "/" + server.maxPlayers) +
|
||||
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>';
|
||||
'<div class="co-preview"><img src="' + escapeAttr(server.previewImage) + '" alt="Preview"></div>' +
|
||||
'<div class="co-preview-caption">' +
|
||||
'<div class="co-preview-name">' + escapeHtml(server.name) + "</div>" +
|
||||
'<div class="co-preview-stats">' + escapeHtml(stats) + "</div>" +
|
||||
"</div>";
|
||||
}
|
||||
};
|
||||
|
||||
function detailRow(label, value) {
|
||||
return '<div class="co-detail-row"><span>' + label + '</span><span>' + escapeHtml(String(value)) + "</span></div>";
|
||||
}
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value)
|
||||
.replace(/&/g, "&")
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
if (!state.servers.length) {
|
||||
list.innerHTML = '<div class="co-empty">No servers found</div>';
|
||||
CO_ServerList.updateScrollIndicator(wrap, list);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -26,16 +27,26 @@
|
||||
var pingCls = CO_ServerList.pingClass(server.ping);
|
||||
return (
|
||||
'<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>" + 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 class="co-ping ' + pingCls + '">' + server.ping + " ms</span>" +
|
||||
"<span>" + escapeHtml(server.worldspace) + "</span>" +
|
||||
"<span>" + escapeHtml(server.mode) + "</span>" +
|
||||
"<span>" + escapeHtml(server.status) + "</span>" +
|
||||
"</div>" +
|
||||
"</div>"
|
||||
);
|
||||
}).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) {
|
||||
@@ -64,6 +75,10 @@
|
||||
if (row) {
|
||||
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,
|
||||
|
||||
render: function (root, state) {
|
||||
var tabsEl = root.querySelector(".co-tabs");
|
||||
if (!tabsEl) return;
|
||||
var navEl = root.querySelector(".co-nav-list");
|
||||
if (!navEl) return;
|
||||
|
||||
var labels = {
|
||||
"direct-connect": "Direct Connect",
|
||||
"server-browser": "Server Browser",
|
||||
"server-browser": "Browse",
|
||||
favorites: "Favorites",
|
||||
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 focused = state.focusZone === "tabs" && state.selectedTabIndex === index ? " focused" : "";
|
||||
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] +
|
||||
"</button>"
|
||||
);
|
||||
}).join("");
|
||||
|
||||
navEl.className = "co-nav-list" + (state.focusZone === "tabs" ? " focused" : "");
|
||||
},
|
||||
|
||||
bind: function (root, onTabClick) {
|
||||
var tabsEl = root.querySelector(".co-tabs");
|
||||
if (!tabsEl) return;
|
||||
var navEl = root.querySelector(".co-nav-list");
|
||||
if (!navEl) return;
|
||||
|
||||
tabsEl.addEventListener("click", function (e) {
|
||||
var btn = e.target.closest(".co-tab");
|
||||
navEl.addEventListener("click", function (e) {
|
||||
var btn = e.target.closest(".co-nav-item");
|
||||
if (!btn) return;
|
||||
onTabClick(btn.getAttribute("data-tab"));
|
||||
});
|
||||
|
||||
@@ -9,63 +9,59 @@
|
||||
<body>
|
||||
<div class="co-root" id="coRoot">
|
||||
<div class="co-panel" id="coPanel">
|
||||
<header class="co-header">
|
||||
<h1>Commonwealth Online</h1>
|
||||
<div class="co-subtitle">Server Browser</div>
|
||||
</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">
|
||||
<polyline class="co-panel-border-line" points="0 10.97 0 0 1075.97 0 1075.97 10.5"/>
|
||||
</svg>
|
||||
<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-tab-content">
|
||||
<div class="co-tab-pane" data-pane="direct-connect"></div>
|
||||
<div class="co-tab-pane active" data-pane="server-browser">
|
||||
<div class="co-browser-layout">
|
||||
<aside class="co-filters"></aside>
|
||||
<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 class="co-subpanel-content">
|
||||
<div class="co-tab-content">
|
||||
<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">
|
||||
<header class="co-subpanel-header">
|
||||
<h2 class="co-subpanel-title" id="coSubpanelTitle">Servers</h2>
|
||||
<div class="co-subpanel-meta">
|
||||
<span id="coConnectionStatus">Online</span>
|
||||
<span class="co-count" id="coServerCount"></span>
|
||||
</div>
|
||||
</header>
|
||||
<div class="co-filters"></div>
|
||||
<div class="co-server-list-wrap">
|
||||
<div class="co-server-list"></div>
|
||||
<div class="co-scroll-indicator" aria-hidden="true">»»</div>
|
||||
</div>
|
||||
<aside class="co-details"></aside>
|
||||
</div>
|
||||
</div>
|
||||
<div class="co-tab-pane" data-pane="favorites">
|
||||
<div class="co-browser-layout co-favorites-layout">
|
||||
|
||||
<div class="co-tab-pane" data-pane="favorites">
|
||||
<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-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-scroll-indicator" aria-hidden="true">»»</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="co-tab-pane" data-pane="recent">
|
||||
<div class="co-browser-layout co-recent-layout">
|
||||
|
||||
<div class="co-tab-pane" data-pane="recent">
|
||||
<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-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-scroll-indicator" aria-hidden="true">»»</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -73,10 +69,6 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
--co-green: #4dff3a;
|
||||
--co-green-soft: rgba(77, 255, 58, 0.88);
|
||||
--co-green-dim: rgba(57, 255, 34, 0.35);
|
||||
--co-bg: rgba(0, 14, 0, 0.92);
|
||||
--co-bg-dark: rgba(0, 8, 0, 0.96);
|
||||
--co-bg-list: rgba(0, 12, 0, 0.88);
|
||||
--co-selected: rgba(77, 255, 58, 0.92);
|
||||
--co-selected-text: #041200;
|
||||
--co-border: rgba(77, 255, 58, 0.9);
|
||||
--co-text-dim: rgba(77, 255, 58, 0.68);
|
||||
--co-ping-good: #4dff3a;
|
||||
--co-ping-mid: #c8ff3a;
|
||||
--co-ping-bad: #ffd43a;
|
||||
--co-green: #2dff28;
|
||||
--co-green-soft: #2dff28;
|
||||
--co-green-dim: rgba(45, 255, 40, 0.22);
|
||||
--co-bg: rgba(0, 12, 0, 0.62);
|
||||
--co-bg-dark: rgba(0, 10, 0, 0.72);
|
||||
--co-bg-list: transparent;
|
||||
--co-selected: #2dff28;
|
||||
--co-selected-text: #001400;
|
||||
--co-border: #2dff28;
|
||||
--co-text-dim: rgba(45, 255, 40, 0.5);
|
||||
--co-ping-good: #2dff28;
|
||||
--co-ping-mid: #c8ff28;
|
||||
--co-ping-bad: #ffd428;
|
||||
--co-danger: #ff6a6a;
|
||||
--co-font: "Consolas", "Courier New", monospace;
|
||||
--co-spacing: 10px;
|
||||
--co-transition: 120ms ease;
|
||||
--co-text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
|
||||
--co-font: "Roboto Condensed", "Bahnschrift", "Arial Narrow", Arial, sans-serif;
|
||||
--co-transition: 80ms linear;
|
||||
--co-border-stroke: 5px;
|
||||
--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):
|
||||
* Left — menu list + green selection bar (~0–46%)
|
||||
* Top — "Fallout 4 Updates" / installed-content panel (~0–26% height)
|
||||
* Bottom — Bethesda logo / controller hints (~0–8% height)
|
||||
* Fallout 4 submenu alignment (1280×720, like ADD-ONS panel):
|
||||
* Left — tight gap, no overlap (~454px)
|
||||
* Top — MULTIPLAYER row (~286px)
|
||||
* 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-top: max(26vh, 220px);
|
||||
--co-panel-bottom: max(8vh, 72px);
|
||||
--co-font-sm: clamp(11px, 0.95vw, 13px);
|
||||
--co-font-md: clamp(13px, 1.1vw, 16px);
|
||||
--co-font-lg: clamp(15px, 1.25vw, 19px);
|
||||
--co-font-xl: clamp(18px, 1.5vw, 24px);
|
||||
--co-panel-top: max(39.5vh, 286px);
|
||||
--co-panel-bottom: max(20.5vh, 146px);
|
||||
--co-font-xs: clamp(9px, 0.75vw, 11px);
|
||||
--co-font-sm: clamp(10px, 0.85vw, 12px);
|
||||
--co-font-md: clamp(13px, 1.05vw, 16px);
|
||||
--co-font-lg: clamp(16px, 1.3vw, 20px);
|
||||
--co-nav-width: clamp(152px, 14vw, 196px);
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -45,9 +56,11 @@ body {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
font-family: var(--co-font);
|
||||
font-weight: 600;
|
||||
color: var(--co-green);
|
||||
background: transparent;
|
||||
user-select: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.co-root {
|
||||
@@ -58,151 +71,271 @@ body {
|
||||
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 {
|
||||
position: absolute;
|
||||
left: var(--co-panel-left);
|
||||
right: var(--co-panel-right);
|
||||
top: var(--co-panel-top);
|
||||
bottom: var(--co-panel-bottom);
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 0;
|
||||
min-height: 360px;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 2px solid var(--co-border);
|
||||
border: none;
|
||||
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;
|
||||
transition: opacity var(--co-transition);
|
||||
backdrop-filter: blur(2px);
|
||||
padding: var(--co-border-cap) var(--co-border-edge);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.co-panel.co-loading {
|
||||
opacity: 0.92;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.co-header {
|
||||
padding: 18px 22px 14px;
|
||||
border-bottom: 1px solid var(--co-green-dim);
|
||||
background: rgba(0, 6, 0, 0.45);
|
||||
/*
|
||||
* Top/bottom border strips (from assets/MenuBoarder.svg).
|
||||
* Split so each edge anchors correctly — a single stretched SVG misaligns in Ultralight.
|
||||
*/
|
||||
.co-panel-border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: var(--co-border-strip-height);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.co-header h1 {
|
||||
font-size: var(--co-font-xl);
|
||||
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-panel-border-top {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.co-header .co-subtitle {
|
||||
margin-top: 6px;
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--co-text-dim);
|
||||
.co-panel-border-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.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;
|
||||
gap: 4px;
|
||||
border-bottom: 1px solid var(--co-green-dim);
|
||||
padding: 0 16px;
|
||||
background: rgba(0, 6, 0, 0.3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.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;
|
||||
border: none;
|
||||
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);
|
||||
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 {
|
||||
color: var(--co-green);
|
||||
border-color: var(--co-border);
|
||||
background: rgba(57, 255, 34, 0.06);
|
||||
.co-nav-item:hover:not(.active) {
|
||||
background: rgba(45, 255, 40, 0.08);
|
||||
}
|
||||
|
||||
.co-tab.focused {
|
||||
outline: 1px solid var(--co-green);
|
||||
outline-offset: -2px;
|
||||
.co-nav-item.active {
|
||||
background: var(--co-selected);
|
||||
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;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.co-browser-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr minmax(240px, 30%);
|
||||
grid-template-rows: auto 1fr;
|
||||
.co-tab-pane {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.co-filters {
|
||||
grid-column: 1 / -1;
|
||||
border-bottom: 1px solid var(--co-green-dim);
|
||||
padding: 12px 16px;
|
||||
.co-tab-pane.active {
|
||||
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-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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.co-filter-row[data-filter-index="0"] {
|
||||
flex: 1 1 180px;
|
||||
min-width: 160px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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 130px;
|
||||
min-width: 110px;
|
||||
.co-filter-row.co-filter-checks {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.co-filter-label {
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.1em;
|
||||
font-size: var(--co-font-xs);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--co-text-dim);
|
||||
}
|
||||
@@ -215,8 +348,13 @@ body {
|
||||
color: var(--co-green);
|
||||
font: inherit;
|
||||
font-size: var(--co-font-sm);
|
||||
padding: 8px 10px;
|
||||
text-shadow: var(--co-text-shadow);
|
||||
font-weight: 600;
|
||||
padding: 5px 8px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.co-filter-input::placeholder {
|
||||
color: var(--co-text-dim);
|
||||
}
|
||||
|
||||
.co-filter-input:focus,
|
||||
@@ -225,30 +363,22 @@ body {
|
||||
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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.05em;
|
||||
gap: 5px;
|
||||
font-size: var(--co-font-xs);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
padding: 8px 0 0;
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.co-checkbox {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border: 1px solid var(--co-border);
|
||||
background: var(--co-bg-dark);
|
||||
background: transparent;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -257,109 +387,81 @@ body {
|
||||
|
||||
.co-checkbox.checked::after {
|
||||
content: "X";
|
||||
font-size: 11px;
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
color: var(--co-green);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.co-btn {
|
||||
appearance: none;
|
||||
border: 1px solid var(--co-border);
|
||||
background: rgba(77, 255, 58, 0.1);
|
||||
border: 1px solid var(--co-green-dim);
|
||||
background: transparent;
|
||||
color: var(--co-green);
|
||||
font: inherit;
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.08em;
|
||||
font-size: var(--co-font-xs);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
padding: 10px 14px;
|
||||
padding: 5px 10px;
|
||||
cursor: pointer;
|
||||
margin-left: auto;
|
||||
align-self: flex-end;
|
||||
flex-shrink: 0;
|
||||
transition: background var(--co-transition);
|
||||
}
|
||||
|
||||
.co-btn:hover {
|
||||
background: rgba(57, 255, 34, 0.18);
|
||||
background: rgba(45, 255, 40, 0.1);
|
||||
}
|
||||
|
||||
.co-btn.focused {
|
||||
background: rgba(45, 255, 40, 0.18);
|
||||
outline: 1px solid var(--co-green);
|
||||
background: rgba(57, 255, 34, 0.22);
|
||||
}
|
||||
|
||||
/* Server list — save-file style rows */
|
||||
.co-server-list-wrap {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
border-right: 1px solid var(--co-green-dim);
|
||||
background: var(--co-bg-list);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.co-server-list-wrap.focused {
|
||||
outline: 1px solid var(--co-green);
|
||||
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);
|
||||
background: rgba(45, 255, 40, 0.03);
|
||||
}
|
||||
|
||||
.co-server-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
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 {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.co-server-list::-webkit-scrollbar-track {
|
||||
background: var(--co-bg-dark);
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.co-server-list::-webkit-scrollbar-thumb {
|
||||
background: var(--co-green-dim);
|
||||
border: 1px solid var(--co-border);
|
||||
}
|
||||
|
||||
.co-server-row {
|
||||
display: grid;
|
||||
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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid rgba(77, 255, 58, 0.1);
|
||||
transition: background var(--co-transition);
|
||||
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) {
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 8px 12px;
|
||||
font-size: var(--co-font-md);
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
cursor: pointer;
|
||||
transition: background var(--co-transition);
|
||||
}
|
||||
|
||||
.co-server-row:hover {
|
||||
background: rgba(57, 255, 34, 0.06);
|
||||
.co-server-row:hover:not(.selected) {
|
||||
background: rgba(45, 255, 40, 0.06);
|
||||
}
|
||||
|
||||
.co-server-row.selected {
|
||||
@@ -367,15 +469,44 @@ body {
|
||||
color: var(--co-selected-text);
|
||||
}
|
||||
|
||||
.co-server-row.selected .co-ping,
|
||||
.co-server-row.selected .co-fav {
|
||||
.co-server-row-left {
|
||||
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);
|
||||
}
|
||||
|
||||
.co-fav {
|
||||
text-align: center;
|
||||
color: var(--co-text-dim);
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.co-fav.on {
|
||||
@@ -396,120 +527,32 @@ body {
|
||||
color: var(--co-selected-text);
|
||||
}
|
||||
|
||||
.co-details {
|
||||
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;
|
||||
.co-scroll-indicator {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.co-detail-row span:last-child {
|
||||
text-align: right;
|
||||
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;
|
||||
text-align: center;
|
||||
padding: 2px 0 4px;
|
||||
font-size: var(--co-font-xs);
|
||||
letter-spacing: 0.15em;
|
||||
color: var(--co-text-dim);
|
||||
font-size: var(--co-font-sm);
|
||||
}
|
||||
|
||||
.co-detail-rules li {
|
||||
margin-bottom: 3px;
|
||||
.co-scroll-indicator.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Footer prompts */
|
||||
.co-action-bar {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 18px;
|
||||
justify-content: flex-end;
|
||||
padding: 6px 14px 8px;
|
||||
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 {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
@@ -517,62 +560,59 @@ body {
|
||||
.co-prompt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.06em;
|
||||
gap: 6px;
|
||||
font-size: var(--co-font-xs);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--co-green-soft);
|
||||
}
|
||||
|
||||
.co-prompt-key {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid var(--co-border);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
font-size: 8px;
|
||||
font-weight: 700;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.co-direct-connect {
|
||||
padding: 28px 36px;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.co-direct-connect h2 {
|
||||
font-size: var(--co-font-lg);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 18px;
|
||||
padding: 16px 20px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.co-direct-row {
|
||||
margin-bottom: 14px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.co-direct-row label {
|
||||
display: block;
|
||||
font-size: var(--co-font-sm);
|
||||
letter-spacing: 0.08em;
|
||||
font-size: var(--co-font-xs);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--co-text-dim);
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.co-empty {
|
||||
padding: 32px;
|
||||
padding: 24px 16px;
|
||||
text-align: center;
|
||||
color: var(--co-text-dim);
|
||||
font-size: var(--co-font-md);
|
||||
letter-spacing: 0.06em;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.co-join-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 10, 0, 0.88);
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@@ -597,10 +637,11 @@ body {
|
||||
}
|
||||
|
||||
.co-spinner {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border: 2px solid var(--co-green-dim);
|
||||
border-top-color: var(--co-green);
|
||||
border-radius: 50%;
|
||||
animation: co-spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@@ -608,68 +649,22 @@ body {
|
||||
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) {
|
||||
:root {
|
||||
--co-panel-left: max(46vw, 520px);
|
||||
--co-panel-left: max(35.8vw, 450px);
|
||||
--co-panel-right: max(2vw, 16px);
|
||||
--co-panel-top: max(28vh, 200px);
|
||||
--co-panel-bottom: max(9vh, 64px);
|
||||
--co-panel-top: max(40vh, 280px);
|
||||
--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) {
|
||||
:root {
|
||||
--co-panel-left: max(44vw, 960px);
|
||||
--co-panel-left: max(35.6vw, 908px);
|
||||
--co-panel-right: max(2.5vw, 48px);
|
||||
--co-panel-top: max(24vh, 280px);
|
||||
--co-panel-bottom: max(7vh, 96px);
|
||||
--co-panel-top: max(39.5vh, 572px);
|
||||
--co-panel-bottom: max(20.5vh, 292px);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user