mirror of
https://github.com/l5yth/potato-mesh.git
synced 2026-07-21 09:02:54 +02:00
feat: add site info overlay (#103)
This commit is contained in:
+83
-1
@@ -69,10 +69,22 @@
|
||||
#map .leaflet-tile { filter: opacity(70%); }
|
||||
#nodes { font-size: 12px; }
|
||||
footer { position: fixed; bottom: 0; left: var(--pad); width: calc(100% - 2 * var(--pad)); background: #fafafa; border-top: 1px solid #ddd; text-align: center; font-size: 12px; padding: 4px 0; }
|
||||
.info-overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; padding: var(--pad); z-index: 1000; }
|
||||
.info-overlay[hidden] { display: none; }
|
||||
.info-dialog { background: #fff; color: #111; max-width: 420px; width: min(100%, 420px); border-radius: 12px; box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2); position: relative; padding: 20px 24px; outline: none; }
|
||||
.info-dialog:focus { outline: 2px solid #4a90e2; outline-offset: 4px; }
|
||||
.info-close { position: absolute; top: 10px; right: 10px; padding: 4px; border: none; background: transparent; font-size: 20px; line-height: 1; border-radius: 999px; }
|
||||
.info-close:hover { background: rgba(0, 0, 0, 0.06); }
|
||||
.info-title { margin: 0 0 8px; font-size: 20px; }
|
||||
.info-intro { margin: 0 0 12px; font-size: 14px; color: #444; }
|
||||
.info-details { margin: 0; font-size: 14px; line-height: 1.6; }
|
||||
.info-details dt { font-weight: 600; margin-top: 12px; color: #222; }
|
||||
.info-details dd { margin: 4px 0 0; }
|
||||
.info-details dd a { color: inherit; word-break: break-word; }
|
||||
@media (max-width: 768px) {
|
||||
.row { flex-direction: column; align-items: stretch; gap: var(--pad); }
|
||||
.map-row { flex-direction: column; }
|
||||
.controls { order: 2; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; width: 100%; gap: 12px; }
|
||||
.controls { order: 2; display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; align-items: center; width: 100%; gap: 12px; }
|
||||
.controls input[type="text"] { width: 100%; }
|
||||
.controls button { justify-self: end; }
|
||||
.meta-info { order: 1; width: 100%; }
|
||||
@@ -119,6 +131,11 @@
|
||||
body.dark .chat-entry-msg { color: #bbb }
|
||||
body.dark .short-name { color: #555 }
|
||||
body.dark .chat-entry-date { color: #bbb }
|
||||
body.dark .info-overlay { background: rgba(0, 0, 0, 0.7); }
|
||||
body.dark .info-dialog { background: #1c1c1c; color: #eee; border: 1px solid #444; }
|
||||
body.dark .info-intro { color: #bbb; }
|
||||
body.dark .info-details dt { color: #ddd; }
|
||||
body.dark .info-close:hover { background: rgba(255, 255, 255, 0.1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -137,6 +154,31 @@
|
||||
<label><input type="checkbox" id="fitBounds" checked /> Auto-fit map</label>
|
||||
<input type="text" id="filterInput" placeholder="Filter nodes" />
|
||||
<button id="themeToggle" type="button" aria-label="Toggle dark mode">🌙</button>
|
||||
<button id="infoBtn" type="button" aria-haspopup="dialog" aria-controls="infoOverlay" aria-label="Show site information">ℹ️ Info</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="infoOverlay" class="info-overlay" role="dialog" aria-modal="true" aria-labelledby="infoTitle" hidden>
|
||||
<div class="info-dialog" tabindex="-1">
|
||||
<button type="button" class="info-close" id="infoClose" aria-label="Close site information">×</button>
|
||||
<h2 id="infoTitle" class="info-title">About <%= site_name %></h2>
|
||||
<p class="info-intro">Quick facts about this PotatoMesh instance.</p>
|
||||
<dl class="info-details">
|
||||
<dt>Default channel</dt>
|
||||
<dd><%= default_channel %></dd>
|
||||
<dt>Frequency</dt>
|
||||
<dd><%= default_frequency %></dd>
|
||||
<dt>Map center</dt>
|
||||
<dd><%= format("%.5f, %.5f", map_center_lat, map_center_lon) %></dd>
|
||||
<dt>Visible range</dt>
|
||||
<dd>Nodes within roughly <%= max_node_distance_km %> km of the center are shown.</dd>
|
||||
<dt>Auto-refresh</dt>
|
||||
<dd>Updates every <%= refresh_interval_seconds %> seconds.</dd>
|
||||
<% if matrix_room && !matrix_room.empty? %>
|
||||
<dt>Matrix room</dt>
|
||||
<dd><a href="https://matrix.to/#/<%= matrix_room %>" target="_blank" rel="noreferrer noopener"><%= matrix_room %></a></dd>
|
||||
<% end %>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -182,6 +224,10 @@
|
||||
const refreshBtn = document.getElementById('refreshBtn');
|
||||
const filterInput = document.getElementById('filterInput');
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const infoBtn = document.getElementById('infoBtn');
|
||||
const infoOverlay = document.getElementById('infoOverlay');
|
||||
const infoClose = document.getElementById('infoClose');
|
||||
const infoDialog = infoOverlay ? infoOverlay.querySelector('.info-dialog') : null;
|
||||
const titleEl = document.querySelector('title');
|
||||
const headerEl = document.querySelector('h1');
|
||||
const chatEl = document.getElementById('chat');
|
||||
@@ -245,6 +291,42 @@
|
||||
tiles.addTo(map);
|
||||
});
|
||||
|
||||
let lastFocusBeforeInfo = null;
|
||||
|
||||
function openInfoOverlay() {
|
||||
if (!infoOverlay || !infoDialog) return;
|
||||
lastFocusBeforeInfo = document.activeElement;
|
||||
infoOverlay.hidden = false;
|
||||
document.body.style.setProperty('overflow', 'hidden');
|
||||
infoDialog.focus();
|
||||
}
|
||||
|
||||
function closeInfoOverlay() {
|
||||
if (!infoOverlay || !infoDialog) return;
|
||||
infoOverlay.hidden = true;
|
||||
document.body.style.removeProperty('overflow');
|
||||
const target = lastFocusBeforeInfo && typeof lastFocusBeforeInfo.focus === 'function' ? lastFocusBeforeInfo : infoBtn;
|
||||
if (target && typeof target.focus === 'function') {
|
||||
target.focus();
|
||||
}
|
||||
lastFocusBeforeInfo = null;
|
||||
}
|
||||
|
||||
if (infoBtn && infoOverlay && infoClose) {
|
||||
infoBtn.addEventListener('click', openInfoOverlay);
|
||||
infoClose.addEventListener('click', closeInfoOverlay);
|
||||
infoOverlay.addEventListener('click', event => {
|
||||
if (event.target === infoOverlay) {
|
||||
closeInfoOverlay();
|
||||
}
|
||||
});
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape' && !infoOverlay.hidden) {
|
||||
closeInfoOverlay();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
|
||||
Reference in New Issue
Block a user