-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathabout-modal.js
More file actions
168 lines (149 loc) · 7.47 KB
/
Copy pathabout-modal.js
File metadata and controls
168 lines (149 loc) · 7.47 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* about-modal.js — shared, self-contained About modal for public.html + live.html.
*
* Served from repo root by express.static(__dirname). No dependencies, no build
* step. Defines a single global `openAboutModal()`. Content is distilled from
* about.html so the two stay in sync conceptually — keep this accurate to that
* page. Styling uses ONLY the CSS tokens both pages already define (no hardcoded
* colors). Backdrop uses --glass-bg; the card uses --bg-card-solid/--border/--text.
*
* Accessibility: role="dialog", aria-modal, aria-label, a focusable close button,
* Escape-to-close (scoped capture-phase listener removed on close), backdrop
* click to close, and focus moved into the modal on open. Idempotent: a second
* call removes any existing instance first, so it never stacks duplicates.
*/
(function () {
'use strict';
var OVERLAY_ID = 'aboutModalOverlay';
// Distilled from about.html. Each section: { h: heading, p: [paragraphs] }.
// Plain text (no HTML) — rendered with textContent so it's injection-safe.
var SECTIONS = [
{
h: 'What this tester does',
p: [
'The Sentinel Node Tester scans the full active node list on the Sentinel chain, connects to each node, sends real traffic through it, and records the result. This is not a chain-only lookup — it is an end-to-end test of the handshake, the tunnel, and the throughput.',
'A full scan of the network is called a batch. Batches run back-to-back during public testing, so the numbers shown are always based on recent, live evidence.'
]
},
{
h: 'On-chain performance oracle',
p: [
'The tester is a primary publisher of node performance and concurrent-user data to the Sentinel chain. Results are committed on-chain so any consumer can ingest them directly via RPC — no off-chain API required.'
]
},
{
h: 'What the numbers mean',
p: [
'Protocol — the VPN transport the node advertises (WireGuard or V2Ray). Connection test — whether the cryptographic handshake completed. Speed — measured download throughput over the live tunnel, in Mbps. Recent uptime — how often recent tests have succeeded. Speed over time — a sparkline of throughput across recent batches. Peers — active connected clients reported by the node at test time.'
]
},
{
h: 'Where the data comes from',
p: [
'The node list is pulled from the Sentinel chain (RPC-first, with LCD fallback across multiple public endpoints). Every result on this site is generated by a real session on that node — nothing is synthetic, and nothing is cached from third parties.'
]
},
{
h: 'What is never shown to the public',
p: [
'The admin wallet, plan IDs, fee-grant internals, and any operator-side controls stay on the admin surface. This public site is a read-only view of network health. There is nothing to click, nothing to start, and nothing to pay — by design.'
]
}
];
function buildSection(section) {
var wrap = document.createElement('section');
wrap.style.cssText = 'margin-top:18px';
var h = document.createElement('h3');
h.textContent = section.h;
h.style.cssText =
'font-size:12px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;' +
'color:var(--accent);margin:0 0 10px';
wrap.appendChild(h);
section.p.forEach(function (text) {
var p = document.createElement('p');
p.textContent = text;
p.style.cssText =
'color:var(--text);font-size:13px;line-height:1.7;margin:0 0 10px';
wrap.appendChild(p);
});
return wrap;
}
window.openAboutModal = function openAboutModal() {
// Idempotent: never stack duplicates.
var existing = document.getElementById(OVERLAY_ID);
if (existing) existing.remove();
var previouslyFocused = document.activeElement;
var overlay = document.createElement('div');
overlay.id = OVERLAY_ID;
overlay.style.cssText =
'position:fixed;inset:0;background:var(--glass-bg);' +
'backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);' +
'z-index:10000;display:flex;align-items:center;justify-content:center;padding:24px;';
var card = document.createElement('div');
card.setAttribute('role', 'dialog');
card.setAttribute('aria-modal', 'true');
card.setAttribute('aria-label', 'About Sentinel Node Tester');
card.tabIndex = -1;
card.style.cssText =
'background:var(--bg-card-solid);border:1px solid var(--border);border-radius:16px;' +
'padding:24px 28px;max-width:720px;width:100%;max-height:85vh;overflow-y:auto;' +
'color:var(--text);font-family:var(--font-display);line-height:1.55';
// Header row: title + close button.
var header = document.createElement('div');
header.style.cssText =
'display:flex;justify-content:space-between;align-items:flex-start;gap:16px;margin-bottom:6px';
var titleWrap = document.createElement('div');
var title = document.createElement('h2');
title.textContent = 'About Sentinel Node Tester';
title.style.cssText =
'font-size:20px;font-weight:700;letter-spacing:-0.2px;margin:0 0 8px;color:var(--text)';
var intro = document.createElement('p');
intro.textContent =
'Sentinel is a decentralized privacy network of independent nodes that carry ' +
'encrypted traffic. This tool measures every node continuously so you know, at a ' +
'glance, which nodes are healthy, fast, and reachable right now.';
intro.style.cssText = 'color:var(--text-dim);font-size:14px;line-height:1.6;margin:0';
titleWrap.appendChild(title);
titleWrap.appendChild(intro);
var closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.setAttribute('aria-label', 'Close');
closeBtn.textContent = '×'; // ×
closeBtn.style.cssText =
'flex-shrink:0;cursor:pointer;background:transparent;border:1px solid var(--border);' +
'color:var(--text-dim);font-size:20px;line-height:1;width:34px;height:34px;border-radius:8px;' +
'display:flex;align-items:center;justify-content:center;';
header.appendChild(titleWrap);
header.appendChild(closeBtn);
card.appendChild(header);
SECTIONS.forEach(function (s) { card.appendChild(buildSection(s)); });
overlay.appendChild(card);
document.body.appendChild(overlay);
function closeAboutModal() {
document.removeEventListener('keydown', onKeydown, true);
overlay.remove();
// Restore focus to the control that opened the modal, if still present.
try {
if (previouslyFocused && typeof previouslyFocused.focus === 'function' &&
document.contains(previouslyFocused)) {
previouslyFocused.focus();
}
} catch (_) { /* focus restoration is best-effort */ }
}
function onKeydown(e) {
if (e.key === 'Escape' || e.key === 'Esc') {
// Scoped to this modal: stop propagation so a page-level Escape handler
// (e.g. a drawer close) doesn't also fire and close a lower layer.
e.stopImmediatePropagation();
e.preventDefault();
closeAboutModal();
}
}
closeBtn.addEventListener('click', closeAboutModal);
overlay.addEventListener('click', function (e) {
if (e.target === overlay) closeAboutModal();
});
document.addEventListener('keydown', onKeydown, true);
// Move focus into the modal so keyboard + screen-reader users land here.
try { closeBtn.focus(); } catch (_) { try { card.focus(); } catch (_) {} }
};
})();