@decentnetwork/lan 0.1.89 → 0.1.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1443 @@
1
+ const ICON_PATHS = {
2
+ // ---- tab bar (the four must feel like one set) ----
3
+ users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
4
+ grid: '<rect x="3" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="3" width="7.5" height="7.5" rx="1.6"/><rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.6"/><rect x="3" y="13.5" width="7.5" height="7.5" rx="1.6"/>',
5
+ wallet: '<path d="M19 7V6a2 2 0 0 0-2-2H6a3 3 0 0 0-3 3v10a3 3 0 0 0 3 3h12a2 2 0 0 0 2-2v-1"/><path d="M3 7a3 3 0 0 0 3 3h13a2 2 0 0 1 2 2v0a2 2 0 0 1-2 2h-3"/><circle cx="16.5" cy="14" r="1.15" fill="currentColor" stroke="none"/>',
6
+ userRound: '<circle cx="12" cy="8" r="4.5"/><path d="M20 21a8 8 0 0 0-16 0"/>',
7
+ // ---- actions / chrome ----
8
+ search: '<circle cx="11" cy="11" r="7.5"/><path d="m21 21-4.3-4.3"/>',
9
+ plus: '<path d="M5 12h14M12 5v14"/>',
10
+ chevronRight: '<path d="m9 18 6-6-6-6"/>',
11
+ chevronLeft: '<path d="m15 18-6-6 6-6"/>',
12
+ info: '<circle cx="12" cy="12" r="9.5"/><path d="M12 16.5v-5M12 7.6h.01"/>',
13
+ more: '<circle cx="5" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="12" cy="12" r="1.4" fill="currentColor" stroke="none"/><circle cx="19" cy="12" r="1.4" fill="currentColor" stroke="none"/>',
14
+ x: '<path d="M18 6 6 18M6 6l12 12"/>',
15
+ check: '<path d="M20 6 9 17l-5-5"/>',
16
+ checkCheck: '<path d="M18 6 7 17l-5-5"/><path d="m22 10-7.6 7.6L13 16"/>',
17
+ copy: '<rect x="9" y="9" width="12" height="12" rx="2.4"/><path d="M6 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v1"/>',
18
+ refresh: '<path d="M21 12a9 9 0 1 1-2.6-6.3"/><path d="M21 4v4.5h-4.5"/>',
19
+ edit: '<path d="M11 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-5"/><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4Z"/>',
20
+ qr: '<rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><path d="M14 14h3v3M21 14v.01M14 21h.01M17 21h.01M21 17v4"/>',
21
+ // ---- app tiles ----
22
+ video: '<path d="m22 8.5-5.4 3.5 5.4 3.5V8.5Z"/><rect x="2" y="6" width="14.5" height="12" rx="3"/>',
23
+ sparkles: '<path d="M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9 12 3.5Z"/><path d="M19 3.5v3.2M20.6 5.1h-3.2"/><path d="M5 16v2.4M6.2 17.2H3.8"/>',
24
+ mic: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>',
25
+ flask: '<path d="M9.5 3v6.2L4.8 17.4A2 2 0 0 0 6.5 20.5h11a2 2 0 0 0 1.7-3.1L14.5 9.2V3"/><path d="M8.5 3h7"/><path d="M7.3 15h9.4"/>',
26
+ headphones: '<path d="M3.5 14v-1.5a8.5 8.5 0 0 1 17 0V14"/><path d="M3.5 14.5a2.5 2.5 0 0 1 2.5-2.5h0a1.5 1.5 0 0 1 1.5 1.5v3.5a1.5 1.5 0 0 1-1.5 1.5h0a2.5 2.5 0 0 1-2.5-2.5Z"/><path d="M20.5 14.5a2.5 2.5 0 0 0-2.5-2.5h0a1.5 1.5 0 0 0-1.5 1.5v3.5a1.5 1.5 0 0 0 1.5 1.5h0a2.5 2.5 0 0 0 2.5-2.5Z"/>',
27
+ globe: '<circle cx="12" cy="12" r="9.5"/><path d="M2.5 12h19"/><path d="M12 2.5a14 14 0 0 1 0 19 14 14 0 0 1 0-19Z"/>',
28
+ store: '<path d="M4 9.5V19a1.5 1.5 0 0 0 1.5 1.5h13A1.5 1.5 0 0 0 20 19V9.5"/><path d="M3 6.5 4.5 3.5h15L21 6.5a2.5 2.5 0 0 1-4.5 1.5 2.5 2.5 0 0 1-4.5 0 2.5 2.5 0 0 1-4.5 0A2.5 2.5 0 0 1 3 6.5Z"/>',
29
+ // ---- chat composer ----
30
+ camera: '<path d="M14.5 4.5h-5L7.2 7.2H4.5A2.5 2.5 0 0 0 2 9.7V18a2.5 2.5 0 0 0 2.5 2.5h15A2.5 2.5 0 0 0 22 18V9.7a2.5 2.5 0 0 0-2.5-2.5h-2.7L14.5 4.5Z"/><circle cx="12" cy="13.5" r="3.2"/>',
31
+ phone: '<path d="M16.5 21a2 2 0 0 0 2-2.2 1.9 1.9 0 0 0-.6-1.2l-2-1.7a2 2 0 0 0-2.4-.1l-.9.6a14 14 0 0 1-5-5l.6-.9a2 2 0 0 0-.1-2.4l-1.7-2a1.9 1.9 0 0 0-1.2-.6A2 2 0 0 0 3 5.5 16 16 0 0 0 16.5 21Z"/>',
32
+ bolt: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
33
+ arrowUp: '<path d="M12 20V5M6 11l6-6 6 6"/>',
34
+ send: '<path d="M21.5 4.5 10.5 13.5"/><path d="M21.5 4.5 14.8 21.5 11 13l-8.5-3.8 19-4.7Z"/>',
35
+ // ---- account ----
36
+ shield: '<path d="M12 21.5s7.5-3.8 7.5-9.5V5.5L12 2.8 4.5 5.5V12c0 5.7 7.5 9.5 7.5 9.5Z"/>',
37
+ download: '<path d="M12 3.5v11M7.5 10l4.5 4.5 4.5-4.5"/><path d="M5 20.5h14"/>',
38
+ migrate: '<path d="M8 4 4 8l4 4"/><path d="M4 8h13a3 3 0 0 1 3 3v1"/><path d="m16 20 4-4-4-4"/><path d="M20 16H7a3 3 0 0 1-3-3v-1"/>',
39
+ starCoin: '<circle cx="12" cy="12" r="9"/><path d="m12 7.2 1.4 2.9 3.2.4-2.3 2.2.6 3.1-2.9-1.5-2.9 1.5.6-3.1L8.4 10.5l3.2-.4L12 7.2Z" fill="currentColor" stroke="none"/>',
40
+ link: '<path d="M10.5 13.5a4 4 0 0 0 5.7 0l2.3-2.3a4 4 0 0 0-5.7-5.7l-1 1"/><path d="M13.5 10.5a4 4 0 0 0-5.7 0l-2.3 2.3a4 4 0 0 0 5.7 5.7l1-1"/>',
41
+ trendingUp: '<path d="M22 7.5 13.5 16l-4.5-4.5L2 18.5"/><path d="M16.5 7.5H22v5.5"/>',
42
+ bell: '<path d="M18 8.5a6 6 0 0 0-12 0c0 6.5-2.5 8-2.5 8h17S18 15 18 8.5Z"/><path d="M13.7 20.5a2 2 0 0 1-3.4 0"/>',
43
+ bot: '<rect x="3.5" y="9" width="17" height="11" rx="3"/><path d="M12 4.5v4.5"/><circle cx="12" cy="3" r="1.4"/><path d="M8.5 14.5h.01M15.5 14.5h.01"/><path d="M2 13.5v3M22 13.5v3"/>',
44
+ scan: '<path d="M3 8V6a3 3 0 0 1 3-3h2"/><path d="M16 3h2a3 3 0 0 1 3 3v2"/><path d="M21 16v2a3 3 0 0 1-3 3h-2"/><path d="M8 21H6a3 3 0 0 1-3-3v-2"/><path d="M7 12h10"/>',
45
+ userPlus: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M19 8v6M22 11h-6"/>',
46
+ at: '<circle cx="12" cy="12" r="4"/><path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.9 7.9"/>',
47
+ keyRound: '<circle cx="7.5" cy="15.5" r="5.5"/><path d="m21 2-9.6 9.6"/><path d="m15.5 7.5 3 3L22 7l-3-3"/>',
48
+ alert: '<path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/><path d="M12 9v4M12 17h.01"/>',
49
+ chevronDown: '<path d="m6 9 6 6 6-6"/>',
50
+ eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>',
51
+ book: '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2Z"/>',
52
+ trash: '<path d="M3 6h18"/><path d="M8 6V4.5A1.5 1.5 0 0 1 9.5 3h5A1.5 1.5 0 0 1 16 4.5V6"/><path d="M18.5 6l-1 13.5a2 2 0 0 1-2 1.5h-7a2 2 0 0 1-2-1.5L5.5 6"/><path d="M10 10.5v6M14 10.5v6"/>',
53
+ arrowDown: '<path d="M12 4v15M6 13l6 6 6-6"/>',
54
+ shrink: '<path d="M4 14h6v6"/><path d="M20 10h-6V4"/><path d="m14 10 7-7"/><path d="m3 21 7-7"/>',
55
+ speaker: '<path d="M11 5 6 9H3v6h3l5 4V5Z"/><path d="M16 9a3 3 0 0 1 0 6"/><path d="M19 7a6 6 0 0 1 0 10"/>',
56
+ micFill: '<rect x="9" y="2.5" width="6" height="11.5" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0"/><path d="M12 17.5v3.5"/>'
57
+ };
58
+ function Icon({ name, size = 24, stroke = 1.75, color = "currentColor", fill = "none", style }) {
59
+ const d = ICON_PATHS[name];
60
+ if (!d) return null;
61
+ return /* @__PURE__ */ React.createElement(
62
+ "svg",
63
+ {
64
+ width: size,
65
+ height: size,
66
+ viewBox: "0 0 24 24",
67
+ fill,
68
+ stroke: color,
69
+ strokeWidth: stroke,
70
+ strokeLinecap: "round",
71
+ strokeLinejoin: "round",
72
+ style: { display: "block", flexShrink: 0, ...style },
73
+ dangerouslySetInnerHTML: { __html: d }
74
+ }
75
+ );
76
+ }
77
+ Object.assign(window, { Icon, ICON_PATHS });
78
+ Object.assign(ICON_PATHS, {
79
+ message: '<path d="M21 12a8.5 8.5 0 0 1-12.2 7.7L3 21l1.3-5.8A8.5 8.5 0 1 1 21 12Z"/>',
80
+ network: '<circle cx="12" cy="5" r="2.4"/><circle cx="5" cy="19" r="2.4"/><circle cx="19" cy="19" r="2.4"/><path d="M10.6 6.9 6.4 16.7M13.4 6.9l4.2 9.8M7.4 19h9.2"/>',
81
+ route: '<circle cx="6.5" cy="18.5" r="2.4"/><circle cx="17.5" cy="5.5" r="2.4"/><path d="M9 18.5h5.5a3.5 3.5 0 0 0 0-7h-4a3.5 3.5 0 0 1 0-7H15"/>',
82
+ globe2: '<circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3a14 14 0 0 1 0 18 14 14 0 0 1 0-18Z"/>',
83
+ server: '<rect x="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><path d="M7 7.5h.01M7 16.5h.01"/>',
84
+ power: '<path d="M12 4v8"/><path d="M7.6 7.2a7 7 0 1 0 8.8 0"/>',
85
+ ban: '<circle cx="12" cy="12" r="9"/><path d="m5.6 5.6 12.8 12.8"/>',
86
+ terminal: '<path d="m4.5 17 6-6-6-6"/><path d="M12 18.5h7.5"/>',
87
+ signal: '<path d="M4.8 12.8a10 10 0 0 1 14.4 0"/><path d="M8.3 16.3a5 5 0 0 1 7.4 0"/><path d="M12 19.6h.01"/>',
88
+ paperclip: '<path d="m20.5 9-8.7 8.7a4.2 4.2 0 0 1-6-6l8.6-8.6a2.8 2.8 0 0 1 4 4l-8.6 8.6a1.4 1.4 0 0 1-2-2l7.9-7.9"/>',
89
+ file: '<path d="M14 3v5h5"/><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z"/>',
90
+ fileText: '<path d="M14 3v5h5"/><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8l-5-5Z"/><path d="M8.5 13h7M8.5 16.5h5"/>',
91
+ image: '<rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9.5" r="1.6"/><path d="m4 17 4.5-4.5a2 2 0 0 1 2.8 0L20 21"/>',
92
+ arrowRight: '<path d="M5 12h13M13 6l6 6-6 6"/>',
93
+ dotFill: '<circle cx="12" cy="12" r="4" fill="currentColor" stroke="none"/>',
94
+ hash: '<path d="M4 9h16M3.5 15h16M10 4 8 20M16 4l-2 16"/>',
95
+ sliders: '<path d="M4 6h11M4 12h7M4 18h13"/><circle cx="18" cy="6" r="2"/><circle cx="13.5" cy="12" r="2"/><circle cx="19" cy="18" r="2"/>',
96
+ unlink: '<path d="M9 15 5.5 18.5a3.5 3.5 0 0 1-5-5L4 10"/><path d="m15 9 3.5-3.5a3.5 3.5 0 0 1 5 5L20 14"/><path d="M8 8 4 4M16 16l4 4M3 11h2M11 3v2"/>',
97
+ zap: '<path d="M13 2 4 13h7l-1 9 9-11h-7l1-9Z"/>',
98
+ clock: '<circle cx="12" cy="12" r="9"/><path d="M12 7v5l3.5 2"/>',
99
+ plug: '<path d="M9 2v6M15 2v6"/><path d="M7 8h10v3a5 5 0 0 1-10 0V8Z"/><path d="M12 16v6"/>'
100
+ });
101
+ function dkHash(s) {
102
+ let h = 2166136261;
103
+ for (let i = 0; i < s.length; i++) {
104
+ h ^= s.charCodeAt(i);
105
+ h = Math.imul(h, 16777619);
106
+ }
107
+ return h >>> 0;
108
+ }
109
+ function dkRng(a) {
110
+ return function() {
111
+ a |= 0;
112
+ a = a + 1831565813 | 0;
113
+ let t = Math.imul(a ^ a >>> 15, 1 | a);
114
+ t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
115
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
116
+ };
117
+ }
118
+ function dkIdenticon(seed) {
119
+ const rnd = dkRng(dkHash(seed || "x"));
120
+ const hue = Math.floor(rnd() * 360);
121
+ const grid = [];
122
+ for (let y = 0; y < 5; y++) {
123
+ const row = [];
124
+ for (let x = 0; x < 3; x++) row.push(rnd() > 0.5);
125
+ grid.push([row[0], row[1], row[2], row[1], row[0]]);
126
+ }
127
+ return { cells: grid, hue };
128
+ }
129
+ function dkClock(ts) {
130
+ if (!ts) return "";
131
+ const d = new Date(ts);
132
+ return String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
133
+ }
134
+ function dkDayLabel(ts) {
135
+ if (!ts) return "Today";
136
+ const d = new Date(ts), now = /* @__PURE__ */ new Date();
137
+ if (d.toDateString() === now.toDateString()) return "Today";
138
+ const y = new Date(now.getTime() - 864e5);
139
+ if (d.toDateString() === y.toDateString()) return "Yesterday";
140
+ return d.toLocaleDateString();
141
+ }
142
+ async function dkGet(path) {
143
+ const r = await fetch(path, { headers: { "cache-control": "no-cache" } });
144
+ return r.json();
145
+ }
146
+ async function dkPost(path, body) {
147
+ const r = await fetch(path, {
148
+ method: "POST",
149
+ headers: { "content-type": "application/json" },
150
+ body: JSON.stringify(body || {})
151
+ });
152
+ try {
153
+ return await r.json();
154
+ } catch (e) {
155
+ return { ok: r.ok };
156
+ }
157
+ }
158
+ const dkApi = {
159
+ send: (userid, text) => dkPost("/api/chat-send", { userid, text }),
160
+ add: (address) => dkPost("/api/add", { address }),
161
+ accept: (userid) => dkPost("/api/accept", { userid }),
162
+ reject: (userid) => dkPost("/api/reject", { userid }),
163
+ remove: (userid) => dkPost("/api/friend-remove", { userid }),
164
+ alias: (userid, alias) => dkPost("/api/friend-alias", { userid, alias }),
165
+ markRead: (userid) => dkPost("/api/chat-mark-read", { userid })
166
+ };
167
+ const DK_ME_FALLBACK = {
168
+ name: "\u2026",
169
+ handle: "@decentnetwork/peer",
170
+ userId: "",
171
+ carrier: "",
172
+ netKey: "",
173
+ ip: "",
174
+ online: false,
175
+ lanVer: "",
176
+ peerVer: "",
177
+ channel: "@next",
178
+ wire: "163"
179
+ };
180
+ function useDaemonData() {
181
+ const [snap, setSnap] = React.useState({
182
+ me: DK_ME_FALLBACK,
183
+ peers: [],
184
+ requests: [],
185
+ exits: [],
186
+ activeExit: null,
187
+ loaded: false
188
+ });
189
+ const [threads, setThreads] = React.useState({});
190
+ const refresh = React.useCallback(async () => {
191
+ var _a;
192
+ try {
193
+ const d = await dkGet("/api/desktop");
194
+ setSnap({
195
+ me: d.me || DK_ME_FALLBACK,
196
+ peers: d.peers || [],
197
+ requests: d.requests || [],
198
+ exits: d.exits || [],
199
+ activeExit: (_a = d.activeExit) != null ? _a : null,
200
+ loaded: true
201
+ });
202
+ } catch (e) {
203
+ }
204
+ }, []);
205
+ React.useEffect(() => {
206
+ refresh();
207
+ const t = setInterval(refresh, 2500);
208
+ return () => clearInterval(t);
209
+ }, [refresh]);
210
+ const loadThread = React.useCallback(async (peerId) => {
211
+ if (!peerId) return;
212
+ try {
213
+ const d = await dkGet("/api/chat-history?peer=" + encodeURIComponent(peerId));
214
+ const arr = d.chats && d.chats[peerId] || [];
215
+ const msgs = arr.map((m) => ({
216
+ from: m.dir === "out" ? "me" : "them",
217
+ time: dkClock(m.ts),
218
+ text: m.text,
219
+ status: m.dir === "out" ? "read" : void 0
220
+ }));
221
+ const withDay = msgs.length ? [{ day: dkDayLabel(arr[0].ts) }].concat(msgs) : [];
222
+ setThreads((t) => Object.assign({}, t, { [peerId]: withDay }));
223
+ } catch (e) {
224
+ }
225
+ }, []);
226
+ return Object.assign({}, snap, { threads, refresh, loadThread });
227
+ }
228
+ Object.assign(window, {
229
+ dkHash,
230
+ dkIdenticon,
231
+ dkClock,
232
+ dkDayLabel,
233
+ dkApi,
234
+ useDaemonData,
235
+ DK_ME_FALLBACK
236
+ });
237
+ function DkIdenticon({ seed, size = 30, radius = 7 }) {
238
+ const { cells, hue } = dkIdenticon(seed);
239
+ const cell = size / 5;
240
+ const fg = `oklch(0.62 0.15 ${hue})`;
241
+ const bg = `oklch(var(--ident-l) 0.05 ${hue})`;
242
+ return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, style: { display: "block", borderRadius: radius } }, /* @__PURE__ */ React.createElement("rect", { width: size, height: size, fill: bg }), cells.map((row, y) => row.map((on, x) => on ? /* @__PURE__ */ React.createElement("rect", { key: `${x}-${y}`, x: x * cell, y: y * cell, width: cell + 0.4, height: cell + 0.4, fill: fg }) : null)));
243
+ }
244
+ function DkBot({ seed, size = 30, radius = 7 }) {
245
+ const hue = dkIdenticon(seed).hue;
246
+ return /* @__PURE__ */ React.createElement("div", { style: {
247
+ width: size,
248
+ height: size,
249
+ borderRadius: radius,
250
+ flexShrink: 0,
251
+ background: `linear-gradient(150deg, oklch(0.6 0.17 ${hue}), oklch(0.5 0.18 ${(hue + 30) % 360}))`,
252
+ display: "flex",
253
+ alignItems: "center",
254
+ justifyContent: "center"
255
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: size * 0.56, color: "#fff", stroke: 1.9 }));
256
+ }
257
+ function DkAvatar({ peer, size = 30, radius = 7, dot = true }) {
258
+ const d = Math.max(8, size * 0.3);
259
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "relative", width: size, height: size, flexShrink: 0 } }, peer.agent ? /* @__PURE__ */ React.createElement(DkBot, { seed: peer.id, size, radius }) : /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId || peer.id, size, radius }), dot && /* @__PURE__ */ React.createElement("span", { style: {
260
+ position: "absolute",
261
+ right: -2,
262
+ bottom: -2,
263
+ width: d,
264
+ height: d,
265
+ borderRadius: 999,
266
+ background: peer.online ? "var(--online)" : "var(--off)",
267
+ border: "2px solid var(--panel)",
268
+ boxSizing: "border-box"
269
+ } }));
270
+ }
271
+ function StatusDot({ online, size = 8 }) {
272
+ return /* @__PURE__ */ React.createElement("span", { style: {
273
+ width: size,
274
+ height: size,
275
+ borderRadius: 999,
276
+ flexShrink: 0,
277
+ background: online ? "var(--online)" : "var(--off)",
278
+ boxShadow: online ? "0 0 0 3px color-mix(in oklab, var(--online), transparent 80%)" : "none"
279
+ } });
280
+ }
281
+ function Mono({ children, dim, size = 12.5, copy, title }) {
282
+ const [hit, setHit] = React.useState(false);
283
+ const onCopy = (e) => {
284
+ e.stopPropagation();
285
+ try {
286
+ navigator.clipboard.writeText(copy === true ? String(children) : copy);
287
+ } catch (e2) {
288
+ }
289
+ setHit(true);
290
+ setTimeout(() => setHit(false), 900);
291
+ };
292
+ return /* @__PURE__ */ React.createElement("span", { title, onClick: copy ? onCopy : void 0, style: {
293
+ fontFamily: "var(--mono)",
294
+ fontSize: size,
295
+ letterSpacing: -0.2,
296
+ color: dim ? "var(--dim)" : "var(--text)",
297
+ cursor: copy ? "pointer" : "default",
298
+ display: "inline-flex",
299
+ alignItems: "center",
300
+ gap: 5,
301
+ whiteSpace: "nowrap"
302
+ } }, children, copy && /* @__PURE__ */ React.createElement(Icon, { name: hit ? "check" : "copy", size: 12, stroke: 2, color: hit ? "var(--online)" : "var(--faint)" }));
303
+ }
304
+ function shortKey(s, head = 6, tail = 5) {
305
+ if (!s || s.length <= head + tail + 1) return s;
306
+ return s.slice(0, head) + "\u2026" + s.slice(-tail);
307
+ }
308
+ function RouteTag({ peer }) {
309
+ if (!peer.online) return /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline");
310
+ const relay = peer.via === "relay";
311
+ const via = peer.via || "online";
312
+ return /* @__PURE__ */ React.createElement(Tag, { tone: relay ? "warn" : "ok" }, peer.ping != null ? `${via} \xB7 ${peer.ping}ms` : via);
313
+ }
314
+ function Tag({ children, tone = "neutral", style }) {
315
+ const tones = {
316
+ neutral: { bg: "var(--chip)", fg: "var(--dim)", bd: "transparent" },
317
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 86%)", fg: "var(--online)", bd: "transparent" },
318
+ warn: { bg: "color-mix(in oklab, var(--warn), transparent 86%)", fg: "var(--warn)", bd: "transparent" },
319
+ off: { bg: "var(--chip)", fg: "var(--faint)", bd: "transparent" },
320
+ accent: { bg: "color-mix(in oklab, var(--accent), transparent 86%)", fg: "var(--accent)", bd: "transparent" },
321
+ danger: { bg: "color-mix(in oklab, var(--danger), transparent 88%)", fg: "var(--danger)", bd: "transparent" }
322
+ };
323
+ const t = tones[tone] || tones.neutral;
324
+ return /* @__PURE__ */ React.createElement("span", { style: {
325
+ display: "inline-flex",
326
+ alignItems: "center",
327
+ gap: 4,
328
+ height: 18,
329
+ padding: "0 6px",
330
+ borderRadius: 4,
331
+ background: t.bg,
332
+ color: t.fg,
333
+ border: `1px solid ${t.bd}`,
334
+ fontFamily: "var(--mono)",
335
+ fontSize: 11,
336
+ fontWeight: 600,
337
+ letterSpacing: -0.1,
338
+ lineHeight: 1,
339
+ whiteSpace: "nowrap",
340
+ ...style
341
+ } }, children);
342
+ }
343
+ function Unread({ n }) {
344
+ if (!n) return null;
345
+ return /* @__PURE__ */ React.createElement("span", { style: {
346
+ minWidth: 18,
347
+ height: 18,
348
+ padding: "0 5px",
349
+ borderRadius: 999,
350
+ flexShrink: 0,
351
+ background: "var(--accent)",
352
+ color: "#fff",
353
+ fontFamily: "var(--mono)",
354
+ fontSize: 11,
355
+ fontWeight: 700,
356
+ lineHeight: "18px",
357
+ textAlign: "center"
358
+ } }, n);
359
+ }
360
+ function Btn({ children, icon, tone = "ghost", onClick, size = "md", title, style }) {
361
+ const tones = {
362
+ ghost: { bg: "transparent", fg: "var(--text)", bd: "var(--line)" },
363
+ solid: { bg: "var(--accent)", fg: "#fff", bd: "transparent" },
364
+ soft: { bg: "var(--chip)", fg: "var(--text)", bd: "transparent" },
365
+ ok: { bg: "color-mix(in oklab, var(--online), transparent 84%)", fg: "var(--online)", bd: "transparent" },
366
+ danger: { bg: "transparent", fg: "var(--danger)", bd: "color-mix(in oklab, var(--danger), transparent 70%)" }
367
+ };
368
+ const t = tones[tone] || tones.ghost;
369
+ const h = size === "sm" ? 26 : size === "lg" ? 36 : 30;
370
+ return /* @__PURE__ */ React.createElement("button", { onClick, title, style: {
371
+ height: h,
372
+ padding: icon && !children ? 0 : "0 11px",
373
+ minWidth: icon && !children ? h : "auto",
374
+ borderRadius: 6,
375
+ cursor: "pointer",
376
+ fontFamily: "var(--mono)",
377
+ fontWeight: 600,
378
+ fontSize: size === "sm" ? 12 : 12.5,
379
+ letterSpacing: -0.1,
380
+ background: t.bg,
381
+ color: t.fg,
382
+ border: `1px solid ${t.bd}`,
383
+ display: "inline-flex",
384
+ alignItems: "center",
385
+ justifyContent: "center",
386
+ gap: 6,
387
+ whiteSpace: "nowrap",
388
+ ...style
389
+ } }, icon && /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2 }), children);
390
+ }
391
+ function Section({ label, count, trailing, style }) {
392
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, padding: "0 2px", ...style } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), count != null && /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, color: "var(--faint)" } }, count), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, height: 1, background: "var(--line)" } }), trailing);
393
+ }
394
+ Object.assign(window, { DkIdenticon, DkBot, DkAvatar, StatusDot, Mono, shortKey, RouteTag, Tag, Unread, Btn, Section });
395
+ const __TWEAKS_STYLE = `
396
+ .twk-panel{position:fixed;right:16px;bottom:16px;z-index:2147483646;width:280px;
397
+ max-height:calc(100vh - 32px);display:flex;flex-direction:column;
398
+ transform:scale(var(--dc-inv-zoom,1));transform-origin:bottom right;
399
+ background:rgba(250,249,247,.78);color:#29261b;
400
+ -webkit-backdrop-filter:blur(24px) saturate(160%);backdrop-filter:blur(24px) saturate(160%);
401
+ border:.5px solid rgba(255,255,255,.6);border-radius:14px;
402
+ box-shadow:0 1px 0 rgba(255,255,255,.5) inset,0 12px 40px rgba(0,0,0,.18);
403
+ font:11.5px/1.4 ui-sans-serif,system-ui,-apple-system,sans-serif;overflow:hidden}
404
+ .twk-hd{display:flex;align-items:center;justify-content:space-between;
405
+ padding:10px 8px 10px 14px;cursor:move;user-select:none}
406
+ .twk-hd b{font-size:12px;font-weight:600;letter-spacing:.01em}
407
+ .twk-x{appearance:none;border:0;background:transparent;color:rgba(41,38,27,.55);
408
+ width:22px;height:22px;border-radius:6px;cursor:default;font-size:13px;line-height:1}
409
+ .twk-x:hover{background:rgba(0,0,0,.06);color:#29261b}
410
+ .twk-body{padding:2px 14px 14px;display:flex;flex-direction:column;gap:10px;
411
+ overflow-y:auto;overflow-x:hidden;min-height:0;
412
+ scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
413
+ .twk-body::-webkit-scrollbar{width:8px}
414
+ .twk-body::-webkit-scrollbar-track{background:transparent;margin:2px}
415
+ .twk-body::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px;
416
+ border:2px solid transparent;background-clip:content-box}
417
+ .twk-body::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.25);
418
+ border:2px solid transparent;background-clip:content-box}
419
+ .twk-row{display:flex;flex-direction:column;gap:5px}
420
+ .twk-row-h{flex-direction:row;align-items:center;justify-content:space-between;gap:10px}
421
+ .twk-lbl{display:flex;justify-content:space-between;align-items:baseline;
422
+ color:rgba(41,38,27,.72)}
423
+ .twk-lbl>span:first-child{font-weight:500}
424
+ .twk-val{color:rgba(41,38,27,.5);font-variant-numeric:tabular-nums}
425
+
426
+ .twk-sect{font-size:10px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
427
+ color:rgba(41,38,27,.45);padding:10px 0 0}
428
+ .twk-sect:first-child{padding-top:0}
429
+
430
+ .twk-field{appearance:none;box-sizing:border-box;width:100%;min-width:0;height:26px;padding:0 8px;
431
+ border:.5px solid rgba(0,0,0,.1);border-radius:7px;
432
+ background:rgba(255,255,255,.6);color:inherit;font:inherit;outline:none}
433
+ .twk-field:focus{border-color:rgba(0,0,0,.25);background:rgba(255,255,255,.85)}
434
+ select.twk-field{padding-right:22px;
435
+ background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'><path fill='rgba(0,0,0,.5)' d='M0 0h10L5 6z'/></svg>");
436
+ background-repeat:no-repeat;background-position:right 8px center}
437
+
438
+ .twk-slider{appearance:none;-webkit-appearance:none;width:100%;height:4px;margin:6px 0;
439
+ border-radius:999px;background:rgba(0,0,0,.12);outline:none}
440
+ .twk-slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;
441
+ width:14px;height:14px;border-radius:50%;background:#fff;
442
+ border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
443
+ .twk-slider::-moz-range-thumb{width:14px;height:14px;border-radius:50%;
444
+ background:#fff;border:.5px solid rgba(0,0,0,.12);box-shadow:0 1px 3px rgba(0,0,0,.2);cursor:default}
445
+
446
+ .twk-seg{position:relative;display:flex;padding:2px;border-radius:8px;
447
+ background:rgba(0,0,0,.06);user-select:none}
448
+ .twk-seg-thumb{position:absolute;top:2px;bottom:2px;border-radius:6px;
449
+ background:rgba(255,255,255,.9);box-shadow:0 1px 2px rgba(0,0,0,.12);
450
+ transition:left .15s cubic-bezier(.3,.7,.4,1),width .15s}
451
+ .twk-seg.dragging .twk-seg-thumb{transition:none}
452
+ .twk-seg button{appearance:none;position:relative;z-index:1;flex:1;border:0;
453
+ background:transparent;color:inherit;font:inherit;font-weight:500;min-height:22px;
454
+ border-radius:6px;cursor:default;padding:4px 6px;line-height:1.2;
455
+ overflow-wrap:anywhere}
456
+
457
+ .twk-toggle{position:relative;width:32px;height:18px;border:0;border-radius:999px;
458
+ background:rgba(0,0,0,.15);transition:background .15s;cursor:default;padding:0}
459
+ .twk-toggle[data-on="1"]{background:#34c759}
460
+ .twk-toggle i{position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:50%;
461
+ background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);transition:transform .15s}
462
+ .twk-toggle[data-on="1"] i{transform:translateX(14px)}
463
+
464
+ .twk-num{display:flex;align-items:center;box-sizing:border-box;min-width:0;height:26px;padding:0 0 0 8px;
465
+ border:.5px solid rgba(0,0,0,.1);border-radius:7px;background:rgba(255,255,255,.6)}
466
+ .twk-num-lbl{font-weight:500;color:rgba(41,38,27,.6);cursor:ew-resize;
467
+ user-select:none;padding-right:8px}
468
+ .twk-num input{flex:1;min-width:0;height:100%;border:0;background:transparent;
469
+ font:inherit;font-variant-numeric:tabular-nums;text-align:right;padding:0 8px 0 0;
470
+ outline:none;color:inherit;-moz-appearance:textfield}
471
+ .twk-num input::-webkit-inner-spin-button,.twk-num input::-webkit-outer-spin-button{
472
+ -webkit-appearance:none;margin:0}
473
+ .twk-num-unit{padding-right:8px;color:rgba(41,38,27,.45)}
474
+
475
+ .twk-btn{appearance:none;height:26px;padding:0 12px;border:0;border-radius:7px;
476
+ background:rgba(0,0,0,.78);color:#fff;font:inherit;font-weight:500;cursor:default}
477
+ .twk-btn:hover{background:rgba(0,0,0,.88)}
478
+ .twk-btn.secondary{background:rgba(0,0,0,.06);color:inherit}
479
+ .twk-btn.secondary:hover{background:rgba(0,0,0,.1)}
480
+
481
+ .twk-swatch{appearance:none;-webkit-appearance:none;width:56px;height:22px;
482
+ border:.5px solid rgba(0,0,0,.1);border-radius:6px;padding:0;cursor:default;
483
+ background:transparent;flex-shrink:0}
484
+ .twk-swatch::-webkit-color-swatch-wrapper{padding:0}
485
+ .twk-swatch::-webkit-color-swatch{border:0;border-radius:5.5px}
486
+ .twk-swatch::-moz-color-swatch{border:0;border-radius:5.5px}
487
+
488
+ .twk-chips{display:flex;gap:6px}
489
+ .twk-chip{position:relative;appearance:none;flex:1;min-width:0;height:46px;
490
+ padding:0;border:0;border-radius:6px;overflow:hidden;cursor:default;
491
+ box-shadow:0 0 0 .5px rgba(0,0,0,.12),0 1px 2px rgba(0,0,0,.06);
492
+ transition:transform .12s cubic-bezier(.3,.7,.4,1),box-shadow .12s}
493
+ .twk-chip:hover{transform:translateY(-1px);
494
+ box-shadow:0 0 0 .5px rgba(0,0,0,.18),0 4px 10px rgba(0,0,0,.12)}
495
+ .twk-chip[data-on="1"]{box-shadow:0 0 0 1.5px rgba(0,0,0,.85),
496
+ 0 2px 6px rgba(0,0,0,.15)}
497
+ .twk-chip>span{position:absolute;top:0;bottom:0;right:0;width:34%;
498
+ display:flex;flex-direction:column;box-shadow:-1px 0 0 rgba(0,0,0,.1)}
499
+ .twk-chip>span>i{flex:1;box-shadow:0 -1px 0 rgba(0,0,0,.1)}
500
+ .twk-chip>span>i:first-child{box-shadow:none}
501
+ .twk-chip svg{position:absolute;top:6px;left:6px;width:13px;height:13px;
502
+ filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}
503
+ `;
504
+ function useTweaks(defaults) {
505
+ const [values, setValues] = React.useState(defaults);
506
+ const setTweak = React.useCallback((keyOrEdits, val) => {
507
+ const edits = typeof keyOrEdits === "object" && keyOrEdits !== null ? keyOrEdits : { [keyOrEdits]: val };
508
+ setValues((prev) => ({ ...prev, ...edits }));
509
+ window.parent.postMessage({ type: "__edit_mode_set_keys", edits }, "*");
510
+ window.dispatchEvent(new CustomEvent("tweakchange", { detail: edits }));
511
+ }, []);
512
+ return [values, setTweak];
513
+ }
514
+ function TweaksPanel({ title = "Tweaks", children }) {
515
+ const [open, setOpen] = React.useState(false);
516
+ const dragRef = React.useRef(null);
517
+ const offsetRef = React.useRef({ x: 16, y: 16 });
518
+ const PAD = 16;
519
+ const clampToViewport = React.useCallback(() => {
520
+ const panel = dragRef.current;
521
+ if (!panel) return;
522
+ const w = panel.offsetWidth, h = panel.offsetHeight;
523
+ const maxRight = Math.max(PAD, window.innerWidth - w - PAD);
524
+ const maxBottom = Math.max(PAD, window.innerHeight - h - PAD);
525
+ offsetRef.current = {
526
+ x: Math.min(maxRight, Math.max(PAD, offsetRef.current.x)),
527
+ y: Math.min(maxBottom, Math.max(PAD, offsetRef.current.y))
528
+ };
529
+ panel.style.right = offsetRef.current.x + "px";
530
+ panel.style.bottom = offsetRef.current.y + "px";
531
+ }, []);
532
+ React.useEffect(() => {
533
+ if (!open) return;
534
+ clampToViewport();
535
+ if (typeof ResizeObserver === "undefined") {
536
+ window.addEventListener("resize", clampToViewport);
537
+ return () => window.removeEventListener("resize", clampToViewport);
538
+ }
539
+ const ro = new ResizeObserver(clampToViewport);
540
+ ro.observe(document.documentElement);
541
+ return () => ro.disconnect();
542
+ }, [open, clampToViewport]);
543
+ React.useEffect(() => {
544
+ const onMsg = (e) => {
545
+ var _a;
546
+ const t = (_a = e == null ? void 0 : e.data) == null ? void 0 : _a.type;
547
+ if (t === "__activate_edit_mode") setOpen(true);
548
+ else if (t === "__deactivate_edit_mode") setOpen(false);
549
+ };
550
+ window.addEventListener("message", onMsg);
551
+ window.parent.postMessage({ type: "__edit_mode_available" }, "*");
552
+ return () => window.removeEventListener("message", onMsg);
553
+ }, []);
554
+ const dismiss = () => {
555
+ setOpen(false);
556
+ window.parent.postMessage({ type: "__edit_mode_dismissed" }, "*");
557
+ };
558
+ const onDragStart = (e) => {
559
+ const panel = dragRef.current;
560
+ if (!panel) return;
561
+ const r = panel.getBoundingClientRect();
562
+ const sx = e.clientX, sy = e.clientY;
563
+ const startRight = window.innerWidth - r.right;
564
+ const startBottom = window.innerHeight - r.bottom;
565
+ const move = (ev) => {
566
+ offsetRef.current = {
567
+ x: startRight - (ev.clientX - sx),
568
+ y: startBottom - (ev.clientY - sy)
569
+ };
570
+ clampToViewport();
571
+ };
572
+ const up = () => {
573
+ window.removeEventListener("mousemove", move);
574
+ window.removeEventListener("mouseup", up);
575
+ };
576
+ window.addEventListener("mousemove", move);
577
+ window.addEventListener("mouseup", up);
578
+ };
579
+ if (!open) return null;
580
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("style", null, __TWEAKS_STYLE), /* @__PURE__ */ React.createElement(
581
+ "div",
582
+ {
583
+ ref: dragRef,
584
+ className: "twk-panel",
585
+ "data-omelette-chrome": "",
586
+ style: { right: offsetRef.current.x, bottom: offsetRef.current.y }
587
+ },
588
+ /* @__PURE__ */ React.createElement("div", { className: "twk-hd", onMouseDown: onDragStart }, /* @__PURE__ */ React.createElement("b", null, title), /* @__PURE__ */ React.createElement(
589
+ "button",
590
+ {
591
+ className: "twk-x",
592
+ "aria-label": "Close tweaks",
593
+ onMouseDown: (e) => e.stopPropagation(),
594
+ onClick: dismiss
595
+ },
596
+ "\u2715"
597
+ )),
598
+ /* @__PURE__ */ React.createElement("div", { className: "twk-body" }, children)
599
+ ));
600
+ }
601
+ function TweakSection({ label, children }) {
602
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "twk-sect" }, label), children);
603
+ }
604
+ function TweakRow({ label, value, children, inline = false }) {
605
+ return /* @__PURE__ */ React.createElement("div", { className: inline ? "twk-row twk-row-h" : "twk-row" }, /* @__PURE__ */ React.createElement("div", { className: "twk-lbl" }, /* @__PURE__ */ React.createElement("span", null, label), value != null && /* @__PURE__ */ React.createElement("span", { className: "twk-val" }, value)), children);
606
+ }
607
+ function TweakSlider({ label, value, min = 0, max = 100, step = 1, unit = "", onChange }) {
608
+ return /* @__PURE__ */ React.createElement(TweakRow, { label, value: `${value}${unit}` }, /* @__PURE__ */ React.createElement(
609
+ "input",
610
+ {
611
+ type: "range",
612
+ className: "twk-slider",
613
+ min,
614
+ max,
615
+ step,
616
+ value,
617
+ onChange: (e) => onChange(Number(e.target.value))
618
+ }
619
+ ));
620
+ }
621
+ function TweakToggle({ label, value, onChange }) {
622
+ return /* @__PURE__ */ React.createElement("div", { className: "twk-row twk-row-h" }, /* @__PURE__ */ React.createElement("div", { className: "twk-lbl" }, /* @__PURE__ */ React.createElement("span", null, label)), /* @__PURE__ */ React.createElement(
623
+ "button",
624
+ {
625
+ type: "button",
626
+ className: "twk-toggle",
627
+ "data-on": value ? "1" : "0",
628
+ role: "switch",
629
+ "aria-checked": !!value,
630
+ onClick: () => onChange(!value)
631
+ },
632
+ /* @__PURE__ */ React.createElement("i", null)
633
+ ));
634
+ }
635
+ function TweakRadio({ label, value, options, onChange }) {
636
+ var _a;
637
+ const trackRef = React.useRef(null);
638
+ const [dragging, setDragging] = React.useState(false);
639
+ const valueRef = React.useRef(value);
640
+ valueRef.current = value;
641
+ const labelLen = (o) => String(typeof o === "object" ? o.label : o).length;
642
+ const maxLen = options.reduce((m, o) => Math.max(m, labelLen(o)), 0);
643
+ const fitsAsSegments = maxLen <= ((_a = { 2: 16, 3: 10 }[options.length]) != null ? _a : 0);
644
+ if (!fitsAsSegments) {
645
+ const resolve = (s) => {
646
+ const m = options.find((o) => String(typeof o === "object" ? o.value : o) === s);
647
+ return m === void 0 ? s : typeof m === "object" ? m.value : m;
648
+ };
649
+ return /* @__PURE__ */ React.createElement(
650
+ TweakSelect,
651
+ {
652
+ label,
653
+ value,
654
+ options,
655
+ onChange: (s) => onChange(resolve(s))
656
+ }
657
+ );
658
+ }
659
+ const opts = options.map((o) => typeof o === "object" ? o : { value: o, label: o });
660
+ const idx = Math.max(0, opts.findIndex((o) => o.value === value));
661
+ const n = opts.length;
662
+ const segAt = (clientX) => {
663
+ const r = trackRef.current.getBoundingClientRect();
664
+ const inner = r.width - 4;
665
+ const i = Math.floor((clientX - r.left - 2) / inner * n);
666
+ return opts[Math.max(0, Math.min(n - 1, i))].value;
667
+ };
668
+ const onPointerDown = (e) => {
669
+ setDragging(true);
670
+ const v0 = segAt(e.clientX);
671
+ if (v0 !== valueRef.current) onChange(v0);
672
+ const move = (ev) => {
673
+ if (!trackRef.current) return;
674
+ const v = segAt(ev.clientX);
675
+ if (v !== valueRef.current) onChange(v);
676
+ };
677
+ const up = () => {
678
+ setDragging(false);
679
+ window.removeEventListener("pointermove", move);
680
+ window.removeEventListener("pointerup", up);
681
+ };
682
+ window.addEventListener("pointermove", move);
683
+ window.addEventListener("pointerup", up);
684
+ };
685
+ return /* @__PURE__ */ React.createElement(TweakRow, { label }, /* @__PURE__ */ React.createElement(
686
+ "div",
687
+ {
688
+ ref: trackRef,
689
+ role: "radiogroup",
690
+ onPointerDown,
691
+ className: dragging ? "twk-seg dragging" : "twk-seg"
692
+ },
693
+ /* @__PURE__ */ React.createElement(
694
+ "div",
695
+ {
696
+ className: "twk-seg-thumb",
697
+ style: {
698
+ left: `calc(2px + ${idx} * (100% - 4px) / ${n})`,
699
+ width: `calc((100% - 4px) / ${n})`
700
+ }
701
+ }
702
+ ),
703
+ opts.map((o) => /* @__PURE__ */ React.createElement("button", { key: o.value, type: "button", role: "radio", "aria-checked": o.value === value }, o.label))
704
+ ));
705
+ }
706
+ function TweakSelect({ label, value, options, onChange }) {
707
+ return /* @__PURE__ */ React.createElement(TweakRow, { label }, /* @__PURE__ */ React.createElement("select", { className: "twk-field", value, onChange: (e) => onChange(e.target.value) }, options.map((o) => {
708
+ const v = typeof o === "object" ? o.value : o;
709
+ const l = typeof o === "object" ? o.label : o;
710
+ return /* @__PURE__ */ React.createElement("option", { key: v, value: v }, l);
711
+ })));
712
+ }
713
+ function TweakText({ label, value, placeholder, onChange }) {
714
+ return /* @__PURE__ */ React.createElement(TweakRow, { label }, /* @__PURE__ */ React.createElement(
715
+ "input",
716
+ {
717
+ className: "twk-field",
718
+ type: "text",
719
+ value,
720
+ placeholder,
721
+ onChange: (e) => onChange(e.target.value)
722
+ }
723
+ ));
724
+ }
725
+ function TweakNumber({ label, value, min, max, step = 1, unit = "", onChange }) {
726
+ const clamp = (n) => {
727
+ if (min != null && n < min) return min;
728
+ if (max != null && n > max) return max;
729
+ return n;
730
+ };
731
+ const startRef = React.useRef({ x: 0, val: 0 });
732
+ const onScrubStart = (e) => {
733
+ e.preventDefault();
734
+ startRef.current = { x: e.clientX, val: value };
735
+ const decimals = (String(step).split(".")[1] || "").length;
736
+ const move = (ev) => {
737
+ const dx = ev.clientX - startRef.current.x;
738
+ const raw = startRef.current.val + dx * step;
739
+ const snapped = Math.round(raw / step) * step;
740
+ onChange(clamp(Number(snapped.toFixed(decimals))));
741
+ };
742
+ const up = () => {
743
+ window.removeEventListener("pointermove", move);
744
+ window.removeEventListener("pointerup", up);
745
+ };
746
+ window.addEventListener("pointermove", move);
747
+ window.addEventListener("pointerup", up);
748
+ };
749
+ return /* @__PURE__ */ React.createElement("div", { className: "twk-num" }, /* @__PURE__ */ React.createElement("span", { className: "twk-num-lbl", onPointerDown: onScrubStart }, label), /* @__PURE__ */ React.createElement(
750
+ "input",
751
+ {
752
+ type: "number",
753
+ value,
754
+ min,
755
+ max,
756
+ step,
757
+ onChange: (e) => onChange(clamp(Number(e.target.value)))
758
+ }
759
+ ), unit && /* @__PURE__ */ React.createElement("span", { className: "twk-num-unit" }, unit));
760
+ }
761
+ function __twkIsLight(hex) {
762
+ const h = String(hex).replace("#", "");
763
+ const x = h.length === 3 ? h.replace(/./g, (c) => c + c) : h.padEnd(6, "0");
764
+ const n = parseInt(x.slice(0, 6), 16);
765
+ if (Number.isNaN(n)) return true;
766
+ const r = n >> 16 & 255, g = n >> 8 & 255, b = n & 255;
767
+ return r * 299 + g * 587 + b * 114 > 148e3;
768
+ }
769
+ const __TwkCheck = ({ light }) => /* @__PURE__ */ React.createElement("svg", { viewBox: "0 0 14 14", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement(
770
+ "path",
771
+ {
772
+ d: "M3 7.2 5.8 10 11 4.2",
773
+ fill: "none",
774
+ strokeWidth: "2.2",
775
+ strokeLinecap: "round",
776
+ strokeLinejoin: "round",
777
+ stroke: light ? "rgba(0,0,0,.78)" : "#fff"
778
+ }
779
+ ));
780
+ function TweakColor({ label, value, options, onChange }) {
781
+ if (!options || !options.length) {
782
+ return /* @__PURE__ */ React.createElement("div", { className: "twk-row twk-row-h" }, /* @__PURE__ */ React.createElement("div", { className: "twk-lbl" }, /* @__PURE__ */ React.createElement("span", null, label)), /* @__PURE__ */ React.createElement(
783
+ "input",
784
+ {
785
+ type: "color",
786
+ className: "twk-swatch",
787
+ value,
788
+ onChange: (e) => onChange(e.target.value)
789
+ }
790
+ ));
791
+ }
792
+ const key = (o) => String(JSON.stringify(o)).toLowerCase();
793
+ const cur = key(value);
794
+ return /* @__PURE__ */ React.createElement(TweakRow, { label }, /* @__PURE__ */ React.createElement("div", { className: "twk-chips", role: "radiogroup" }, options.map((o, i) => {
795
+ const colors = Array.isArray(o) ? o : [o];
796
+ const [hero, ...rest] = colors;
797
+ const sup = rest.slice(0, 4);
798
+ const on = key(o) === cur;
799
+ return /* @__PURE__ */ React.createElement(
800
+ "button",
801
+ {
802
+ key: i,
803
+ type: "button",
804
+ className: "twk-chip",
805
+ role: "radio",
806
+ "aria-checked": on,
807
+ "data-on": on ? "1" : "0",
808
+ "aria-label": colors.join(", "),
809
+ title: colors.join(" \xB7 "),
810
+ style: { background: hero },
811
+ onClick: () => onChange(o)
812
+ },
813
+ sup.length > 0 && /* @__PURE__ */ React.createElement("span", null, sup.map((c, j) => /* @__PURE__ */ React.createElement("i", { key: j, style: { background: c } }))),
814
+ on && /* @__PURE__ */ React.createElement(__TwkCheck, { light: __twkIsLight(hero) })
815
+ );
816
+ })));
817
+ }
818
+ function TweakButton({ label, onClick, secondary = false }) {
819
+ return /* @__PURE__ */ React.createElement(
820
+ "button",
821
+ {
822
+ type: "button",
823
+ className: secondary ? "twk-btn secondary" : "twk-btn",
824
+ onClick
825
+ },
826
+ label
827
+ );
828
+ }
829
+ Object.assign(window, {
830
+ useTweaks,
831
+ TweaksPanel,
832
+ TweakSection,
833
+ TweakRow,
834
+ TweakSlider,
835
+ TweakToggle,
836
+ TweakRadio,
837
+ TweakSelect,
838
+ TweakText,
839
+ TweakNumber,
840
+ TweakColor,
841
+ TweakButton
842
+ });
843
+ function RequestsBlock({ T, requests, onAct }) {
844
+ if (!requests.length) return null;
845
+ return /* @__PURE__ */ React.createElement("div", { style: { padding: "4px 0 8px" } }, /* @__PURE__ */ React.createElement(Section, { label: T.requests, count: requests.length, style: { margin: "4px 4px 8px" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 6 } }, requests.map((r) => /* @__PURE__ */ React.createElement("div", { key: r.id, style: {
846
+ padding: "9px 10px",
847
+ borderRadius: 8,
848
+ background: "var(--panel-2)",
849
+ border: "1px solid var(--line)",
850
+ display: "flex",
851
+ flexDirection: "column",
852
+ gap: 8
853
+ } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: r.carrier, size: 26, radius: 6 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React.createElement(Mono, { size: 12, copy: r.carrier, title: r.carrier }, shortKey(r.carrier, 8, 6)), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)", marginTop: 2 } }, "via ", r.via, " \xB7 ", r.time))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onAct(r.id, "accept"), style: { flex: 1 } }, T.accept), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "x", size: "sm", onClick: () => onAct(r.id, "reject"), style: { flex: 1 } }, T.reject))))));
854
+ }
855
+ function PeerRow({ peer, T, active, onClick }) {
856
+ const name = peer.alias || peer.userId;
857
+ return /* @__PURE__ */ React.createElement("button", { onClick, style: {
858
+ display: "flex",
859
+ alignItems: "center",
860
+ gap: 10,
861
+ width: "100%",
862
+ textAlign: "left",
863
+ padding: "var(--row-pad)",
864
+ borderRadius: 8,
865
+ cursor: "pointer",
866
+ fontFamily: "inherit",
867
+ border: "1px solid " + (active ? "var(--line)" : "transparent"),
868
+ background: active ? "var(--panel-2)" : "transparent"
869
+ } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 32, radius: 7 }), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6 } }, /* @__PURE__ */ React.createElement("span", { style: {
870
+ fontFamily: peer.alias ? "var(--ui)" : "var(--mono)",
871
+ fontSize: 13.5,
872
+ fontWeight: 600,
873
+ color: "var(--text)",
874
+ whiteSpace: "nowrap",
875
+ overflow: "hidden",
876
+ textOverflow: "ellipsis"
877
+ } }, peer.alias || shortKey(peer.userId, 8, 5)), peer.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 13, color: "var(--faint)", stroke: 2 }), peer.wire === "64" && /* @__PURE__ */ React.createElement(Tag, { tone: "off", style: { height: 15, fontSize: 9.5, padding: "0 4px" } }, "w64")), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 6, marginTop: 2 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)", fontSize: 11 } }, "\xB7"), /* @__PURE__ */ React.createElement("span", { style: {
878
+ fontSize: 11,
879
+ color: "var(--dim)",
880
+ whiteSpace: "nowrap",
881
+ overflow: "hidden",
882
+ textOverflow: "ellipsis",
883
+ flex: 1,
884
+ minWidth: 0
885
+ } }, peer.lastMsg))), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 4, flexShrink: 0 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, peer.lastTime), peer.unread ? /* @__PURE__ */ React.createElement(Unread, { n: peer.unread }) : /* @__PURE__ */ React.createElement(StatusDot, { online: peer.online })));
886
+ }
887
+ function PeerSidebar({ T, peers, requests, activeId, onSelect, onAct, onAdd }) {
888
+ const [q, setQ] = React.useState("");
889
+ const [addr, setAddr] = React.useState("");
890
+ const submitAddr = () => {
891
+ if (addr.trim()) {
892
+ onAdd(addr);
893
+ setAddr("");
894
+ }
895
+ };
896
+ const filtered = peers.filter((p) => {
897
+ if (!q) return true;
898
+ const s = (p.alias || "") + " " + p.userId + " " + p.ip;
899
+ return s.toLowerCase().includes(q.toLowerCase());
900
+ });
901
+ const online = peers.filter((p) => p.online).length;
902
+ return /* @__PURE__ */ React.createElement("div", { style: { width: 320, flexShrink: 0, borderRight: "1px solid var(--line)", display: "flex", flexDirection: "column", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { padding: "12px 12px 10px", borderBottom: "1px solid var(--line)", display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 7 } }, /* @__PURE__ */ React.createElement("input", { value: addr, onChange: (e) => setAddr(e.target.value), onKeyDown: (e) => {
903
+ if (e.key === "Enter") submitAddr();
904
+ }, placeholder: T.addPlaceholder, style: inputStyle }), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "userPlus", onClick: submitAddr }, T.add)), /* @__PURE__ */ React.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center" } }, /* @__PURE__ */ React.createElement(Icon, { name: "search", size: 15, color: "var(--faint)", stroke: 2, style: { position: "absolute", left: 9 } }), /* @__PURE__ */ React.createElement("input", { value: q, onChange: (e) => setQ(e.target.value), placeholder: T.search, style: { ...inputStyle, paddingLeft: 30 } }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", padding: "10px 8px 16px" } }, /* @__PURE__ */ React.createElement(RequestsBlock, { T, requests, onAct }), /* @__PURE__ */ React.createElement(Section, { label: T.peers, count: `${online}/${peers.length}`, style: { margin: "6px 4px 8px" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 2 } }, filtered.map((p) => /* @__PURE__ */ React.createElement(PeerRow, { key: p.id, peer: p, T, active: p.id === activeId, onClick: () => onSelect(p.id) })), !filtered.length && /* @__PURE__ */ React.createElement("div", { style: { padding: 16, textAlign: "center", fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "no matches"))));
905
+ }
906
+ const inputStyle = {
907
+ flex: 1,
908
+ height: 32,
909
+ borderRadius: 7,
910
+ border: "1px solid var(--line)",
911
+ background: "var(--panel-2)",
912
+ color: "var(--text)",
913
+ fontFamily: "var(--mono)",
914
+ fontSize: 12.5,
915
+ padding: "0 10px",
916
+ outline: "none",
917
+ minWidth: 0
918
+ };
919
+ function Msg({ m, peer, T }) {
920
+ const mine = m.from === "me";
921
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: mine ? "flex-end" : "flex-start", alignItems: "flex-end", gap: 8, margin: "3px 0" } }, !mine && /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 24, radius: 6, dot: false }), /* @__PURE__ */ React.createElement("div", { style: { maxWidth: "64%", display: "flex", flexDirection: "column", alignItems: mine ? "flex-end" : "flex-start" } }, m.file ? /* @__PURE__ */ React.createElement("div", { style: {
922
+ display: "flex",
923
+ alignItems: "center",
924
+ gap: 10,
925
+ padding: "10px 12px",
926
+ borderRadius: 10,
927
+ background: mine ? "var(--bub-me)" : "var(--bub-them)",
928
+ border: "1px solid " + (mine ? "transparent" : "var(--line)"),
929
+ minWidth: 200
930
+ } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 7, flexShrink: 0, background: mine ? "rgba(255,255,255,0.16)" : "var(--chip)", display: "flex", alignItems: "center", justifyContent: "center", color: mine ? "#fff" : "var(--accent)" } }, /* @__PURE__ */ React.createElement(Icon, { name: m.file.kind || "file", size: 18, stroke: 1.9 })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0, flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: mine ? "#fff" : "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, m.file.name), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: mine ? "rgba(255,255,255,0.7)" : "var(--faint)", marginTop: 1 } }, m.file.size)), /* @__PURE__ */ React.createElement(Icon, { name: "download", size: 16, stroke: 2, color: mine ? "rgba(255,255,255,0.85)" : "var(--dim)" })) : /* @__PURE__ */ React.createElement("div", { style: {
931
+ padding: "8px 12px",
932
+ borderRadius: 12,
933
+ borderBottomRightRadius: mine ? 4 : 12,
934
+ borderBottomLeftRadius: mine ? 12 : 4,
935
+ background: mine ? "var(--bub-me)" : "var(--bub-them)",
936
+ color: mine ? "#fff" : "var(--text)",
937
+ border: "1px solid " + (mine ? "transparent" : "var(--line)"),
938
+ fontFamily: "var(--ui)",
939
+ fontSize: 13.5,
940
+ lineHeight: 1.4,
941
+ letterSpacing: -0.1,
942
+ wordBreak: "break-word"
943
+ } }, m.text), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 4, margin: "3px 3px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10, color: "var(--faint)" } }, m.time), mine && m.status && /* @__PURE__ */ React.createElement(Icon, { name: "checkCheck", size: 12, stroke: 2.2, color: m.status === "read" ? "var(--accent)" : "var(--faint)" }))));
944
+ }
945
+ function Conversation({ T, peer, lang, thread: threadProp, onSend, onAlias, onRemove, onBlock, onOpenNet, onCall }) {
946
+ const scrollRef = React.useRef(null);
947
+ const thread = threadProp && threadProp.length ? threadProp : [{ day: "Today" }, { from: "them", time: "\u2014", text: lang === "zh" ? "\u6682\u65E0\u6D88\u606F\u8BB0\u5F55\uFF0C\u53D1\u4E2A\u6D88\u606F\u6253\u4E2A\u62DB\u547C\u5427\u3002" : "No messages yet. Say hi." }];
948
+ React.useEffect(() => {
949
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
950
+ }, [peer.id, threadProp]);
951
+ const [menu, setMenu] = React.useState(false);
952
+ const [draft, setDraft] = React.useState("");
953
+ React.useEffect(() => {
954
+ setDraft("");
955
+ }, [peer.id]);
956
+ const sendDraft = () => {
957
+ if (draft.trim()) {
958
+ onSend(draft);
959
+ setDraft("");
960
+ }
961
+ };
962
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 60, flexShrink: 0, borderBottom: "1px solid var(--line)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer, size: 34, radius: 8 }), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: peer.alias ? "var(--ui)" : "var(--mono)", fontSize: 15, fontWeight: 700, color: "var(--text)" } }, peer.alias || shortKey(peer.userId, 10, 6)), peer.agent && /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, "agent"), /* @__PURE__ */ React.createElement(RouteTag, { peer })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 2 } }, /* @__PURE__ */ React.createElement(Mono, { size: 11.5, dim: true, copy: peer.userId, title: peer.userId }, shortKey(peer.userId, 10, 6)), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("button", { onClick: () => onOpenNet(peer), style: { background: "none", border: "none", cursor: "pointer", padding: 0, fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--accent)", display: "inline-flex", alignItems: "center", gap: 4 } }, /* @__PURE__ */ React.createElement(Icon, { name: "network", size: 12, stroke: 2 }), " ", peer.ip))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { icon: "phone", title: T.call, onClick: () => onCall(peer) }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(Btn, { icon: "more", onClick: () => setMenu((v) => !v) }), menu && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { onClick: () => setMenu(false), style: { position: "fixed", inset: 0, zIndex: 40 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 0, top: 36, zIndex: 50, width: 180, background: "var(--panel-2)", border: "1px solid var(--line)", borderRadius: 9, padding: 6, boxShadow: "0 14px 40px rgba(0,0,0,0.4)" } }, /* @__PURE__ */ React.createElement(MenuItem, { icon: "phone", label: T.call, onClick: () => {
963
+ setMenu(false);
964
+ onCall(peer);
965
+ } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "hash", label: T.alias, onClick: () => {
966
+ setMenu(false);
967
+ onAlias(peer);
968
+ } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "file", label: T.sendFile, onClick: () => setMenu(false) }), /* @__PURE__ */ React.createElement("div", { style: { height: 1, background: "var(--line)", margin: "5px 4px" } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "ban", label: T.block, danger: true, onClick: () => {
969
+ setMenu(false);
970
+ onBlock(peer);
971
+ } }), /* @__PURE__ */ React.createElement(MenuItem, { icon: "trash", label: T.remove, danger: true, onClick: () => {
972
+ setMenu(false);
973
+ onRemove(peer);
974
+ } }))))), /* @__PURE__ */ React.createElement("div", { ref: scrollRef, style: { flex: 1, overflow: "auto", padding: "18px 22px" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto" } }, thread.map((m, i) => m.day ? /* @__PURE__ */ React.createElement("div", { key: i, style: { display: "flex", justifyContent: "center", margin: "14px 0" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 600, color: "var(--faint)", background: "var(--chip)", padding: "3px 10px", borderRadius: 999 } }, m.day)) : /* @__PURE__ */ React.createElement(Msg, { key: i, m, peer, T })))), /* @__PURE__ */ React.createElement("div", { style: { flexShrink: 0, borderTop: "1px solid var(--line)", padding: "12px 16px", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "paperclip", title: T.attach }), /* @__PURE__ */ React.createElement(
975
+ "input",
976
+ {
977
+ value: draft,
978
+ onChange: (e) => setDraft(e.target.value),
979
+ onKeyDown: (e) => {
980
+ if (e.key === "Enter" && !e.shiftKey) {
981
+ e.preventDefault();
982
+ sendDraft();
983
+ }
984
+ },
985
+ placeholder: `${T.message} ${peer.alias || shortKey(peer.userId, 6, 4)}\u2026`,
986
+ style: { flex: 1, height: 38, borderRadius: 9, border: "1px solid var(--line)", background: "var(--panel-2)", color: "var(--text)", fontFamily: "var(--ui)", fontSize: 13.5, padding: "0 14px", outline: "none", minWidth: 0 }
987
+ }
988
+ ), /* @__PURE__ */ React.createElement(Btn, { tone: "solid", icon: "arrowUp", title: T.send, onClick: sendDraft }))));
989
+ }
990
+ function MenuItem({ icon, label, onClick, danger }) {
991
+ return /* @__PURE__ */ React.createElement("button", { onClick, style: {
992
+ display: "flex",
993
+ alignItems: "center",
994
+ gap: 10,
995
+ width: "100%",
996
+ textAlign: "left",
997
+ padding: "8px 9px",
998
+ borderRadius: 6,
999
+ border: "none",
1000
+ cursor: "pointer",
1001
+ background: "transparent",
1002
+ fontFamily: "var(--mono)",
1003
+ fontSize: 12.5,
1004
+ fontWeight: 600,
1005
+ color: danger ? "var(--danger)" : "var(--text)"
1006
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 15, stroke: 2, color: danger ? "var(--danger)" : "var(--dim)" }), " ", label);
1007
+ }
1008
+ function ChatEmpty({ T }) {
1009
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 12, color: "var(--faint)", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "message", size: 40, stroke: 1.4, color: "var(--line)" }), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13 } }, T.pickPeer));
1010
+ }
1011
+ function ChatTab({ T, lang, peers, requests, activeId, onSelect, onAct, onRemove, onBlock, onOpenNet, onCall }) {
1012
+ const peer = peers.find((p) => p.id === activeId);
1013
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", minWidth: 0 } }, /* @__PURE__ */ React.createElement(PeerSidebar, { T, peers, requests, activeId, onSelect, onAct }), peer ? /* @__PURE__ */ React.createElement(Conversation, { T, peer, lang, onRemove, onBlock, onOpenNet, onCall }) : /* @__PURE__ */ React.createElement(ChatEmpty, { T }));
1014
+ }
1015
+ Object.assign(window, { ChatTab });
1016
+ function StatTile({ label, value, sub, tone }) {
1017
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, padding: "13px 15px", borderRadius: 10, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: 1, textTransform: "uppercase", color: "var(--faint)" } }, label), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 21, fontWeight: 700, letterSpacing: -0.5, marginTop: 5, color: tone || "var(--text)" } }, value), sub && /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--dim)", marginTop: 2 } }, sub));
1018
+ }
1019
+ function MyNode({ T, me, activeExit, peers, reqCount = 0 }) {
1020
+ const online = peers.filter((p) => p.online).length;
1021
+ const direct = peers.filter((p) => p.online && p.via === "direct").length;
1022
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "16px 18px", borderRadius: 12, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 46, radius: 11 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 14, height: 14, borderRadius: 999, background: "var(--online)", border: "2.5px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 9 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 17, fontWeight: 700, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online"), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel)), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, marginTop: 5 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, T.myIp), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: me.ip }, me.ip), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, "wire ", me.wire), /* @__PURE__ */ React.createElement("span", { style: { color: "var(--line)" } }, "\xB7"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: "var(--faint)" } }, "lan ", me.lanVer, " / peer ", me.peerVer))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { icon: "copy", onClick: () => {
1023
+ try {
1024
+ navigator.clipboard.writeText(me.carrier);
1025
+ } catch (e) {
1026
+ }
1027
+ } }, T.copyAddr)), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 12 } }, /* @__PURE__ */ React.createElement(StatTile, { label: T.peersOnline, value: `${online}/${peers.length}`, sub: `${direct} ${T.direct}`, tone: "var(--online)" }), /* @__PURE__ */ React.createElement(StatTile, { label: T.activeEgress, value: activeExit ? activeExit : T.directEgress, sub: activeExit ? "china \xB7 cn-sh-01" : T.noProxy, tone: activeExit ? "var(--warn)" : "var(--text)" }), /* @__PURE__ */ React.createElement(StatTile, { label: T.wireLabel, value: me.wire, sub: `${me.channel} \xB7 ${T.lossless}` }), /* @__PURE__ */ React.createElement(StatTile, { label: T.reqs, value: String(reqCount), sub: T.pending, tone: reqCount ? "var(--accent)" : "var(--text)" })));
1028
+ }
1029
+ function PeerTable({ T, peers, onOpenChat }) {
1030
+ return /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1.4fr 1fr 1fr 0.8fr 90px", gap: 0, padding: "9px 16px", borderBottom: "1px solid var(--line)", background: "var(--panel-2)" } }, [T.colPeer, T.colVip, T.colPath, T.colWire, ""].map((h, i) => /* @__PURE__ */ React.createElement("span", { key: i, style: { fontFamily: "var(--mono)", fontSize: 10.5, fontWeight: 700, letterSpacing: 0.8, textTransform: "uppercase", color: "var(--faint)", textAlign: i === 4 ? "right" : "left" } }, h))), peers.map((p, i) => /* @__PURE__ */ React.createElement("div", { key: p.id, style: {
1031
+ display: "grid",
1032
+ gridTemplateColumns: "1.4fr 1fr 1fr 0.8fr 90px",
1033
+ gap: 0,
1034
+ alignItems: "center",
1035
+ padding: "11px 16px",
1036
+ borderBottom: i < peers.length - 1 ? "1px solid var(--line)" : "none"
1037
+ } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, minWidth: 0 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: p, size: 26, radius: 6 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: p.alias ? "var(--ui)" : "var(--mono)", fontSize: 13, fontWeight: 600, color: "var(--text)", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" } }, p.alias || shortKey(p.userId, 8, 5)), p.agent && /* @__PURE__ */ React.createElement(Icon, { name: "bot", size: 12, color: "var(--faint)", stroke: 2 })), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, dim: true, copy: p.ip }, p.ip), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(RouteTag, { peer: p })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: p.wire === "64" ? "var(--warn)" : "var(--dim)" } }, p.wire === "64" ? "old\xB764" : "new\xB7163"), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", justifyContent: "flex-end", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "message", size: "sm", title: T.openChat, onClick: () => onOpenChat(p.id) }), /* @__PURE__ */ React.createElement(Btn, { icon: "signal", size: "sm", title: T.ping })))));
1038
+ }
1039
+ function ExitCard({ T, region, activeExit, onSetExit }) {
1040
+ return /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderBottom: "1px solid var(--line)", background: "var(--panel-2)" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, fontWeight: 700, letterSpacing: 0.5, padding: "2px 7px", borderRadius: 5, background: "var(--chip)", color: "var(--dim)" } }, region.flag), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--ui)", fontSize: 13.5, fontWeight: 600, color: "var(--text)" } }, region.label), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, region.nodes.filter((n) => n.online).length, "/", region.nodes.length, " ", T.up)), /* @__PURE__ */ React.createElement("div", null, region.nodes.map((n, i) => {
1041
+ const active = n.ip === activeExit;
1042
+ return /* @__PURE__ */ React.createElement("div", { key: n.ip, style: { display: "flex", alignItems: "center", gap: 12, padding: "10px 14px", borderBottom: i < region.nodes.length - 1 ? "1px solid var(--line)" : "none", background: active ? "color-mix(in oklab, var(--accent), transparent 92%)" : "transparent" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: n.online }), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: n.ip }, n.ip), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11, color: "var(--faint)" } }, n.host), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), n.online ? /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 11.5, color: n.ping > 200 ? "var(--warn)" : "var(--dim)" } }, n.ping, "ms") : /* @__PURE__ */ React.createElement(Tag, { tone: "off" }, "offline"), active ? /* @__PURE__ */ React.createElement(Btn, { tone: "ok", icon: "check", size: "sm", onClick: () => onSetExit(null) }, T.routing) : /* @__PURE__ */ React.createElement(Btn, { size: "sm", icon: "route", onClick: () => n.online && onSetExit(n.ip), style: { opacity: n.online ? 1 : 0.4 } }, T.routeThru));
1043
+ })));
1044
+ }
1045
+ function NetworkTab({ T, me, peers, exits, activeExit, reqCount, onSetExit, onOpenChat }) {
1046
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 1040, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 26 } }, /* @__PURE__ */ React.createElement(MyNode, { T, me, activeExit, peers, reqCount }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.peerRouting, count: peers.length }), /* @__PURE__ */ React.createElement(PeerTable, { T, peers, onOpenChat })), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React.createElement(Section, { label: T.exitNodes, trailing: /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6 } }, /* @__PURE__ */ React.createElement(Btn, { icon: "plus", size: "sm" }, T.addExit)) }), activeExit && /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "11px 14px", borderRadius: 10, background: "color-mix(in oklab, var(--warn), transparent 90%)", border: "1px solid color-mix(in oklab, var(--warn), transparent 70%)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "route", size: 17, color: "var(--warn)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--text)" } }, T.egressVia), /* @__PURE__ */ React.createElement(Mono, { size: 13, copy: activeExit }, activeExit), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", icon: "unlink", size: "sm", onClick: () => onSetExit(null) }, T.stopRouting)), /* @__PURE__ */ React.createElement("div", { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 } }, exits.map((r) => /* @__PURE__ */ React.createElement(ExitCard, { key: r.region, T, region: r, activeExit, onSetExit }))))));
1047
+ }
1048
+ Object.assign(window, { NetworkTab });
1049
+ function FieldRow({ label, value, mono = true, copy, qr, last }) {
1050
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 14, padding: "13px 16px", borderBottom: last ? "none" : "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("span", { style: { width: 130, flexShrink: 0, fontFamily: "var(--mono)", fontSize: 11.5, fontWeight: 600, color: "var(--faint)", textTransform: "uppercase", letterSpacing: 0.5 } }, label), /* @__PURE__ */ React.createElement("span", { style: { flex: 1, minWidth: 0, fontFamily: mono ? "var(--mono)" : "var(--ui)", fontSize: 13.5, color: "var(--text)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, value), copy && /* @__PURE__ */ React.createElement(Btn, { icon: "copy", size: "sm", onClick: () => {
1051
+ try {
1052
+ navigator.clipboard.writeText(value);
1053
+ } catch (e) {
1054
+ }
1055
+ } }), qr && /* @__PURE__ */ React.createElement(Btn, { icon: "qr", size: "sm" }));
1056
+ }
1057
+ function Card({ label, children, trailing }) {
1058
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10 } }, /* @__PURE__ */ React.createElement(Section, { label, trailing }), /* @__PURE__ */ React.createElement("div", { style: { borderRadius: 11, border: "1px solid var(--line)", overflow: "hidden", background: "var(--panel)" } }, children));
1059
+ }
1060
+ function ProfileTab({ T, me }) {
1061
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, overflow: "auto", background: "var(--bg)" } }, /* @__PURE__ */ React.createElement("div", { style: { maxWidth: 760, margin: "0 auto", padding: "24px 28px 60px", display: "flex", flexDirection: "column", gap: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 18, padding: "20px 22px", borderRadius: 14, background: "var(--panel)", border: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkIdenticon, { seed: me.userId, size: 68, radius: 16 }), /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", right: -3, bottom: -3, width: 18, height: 18, borderRadius: 999, background: "var(--online)", border: "3px solid var(--panel)" } })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, letterSpacing: -0.5, color: "var(--text)" } }, me.name), /* @__PURE__ */ React.createElement(Tag, { tone: "ok" }, "online")), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13, color: "var(--dim)", marginTop: 4 } }, me.handle)), /* @__PURE__ */ React.createElement(Btn, { icon: "edit" }, T.editProfile)), /* @__PURE__ */ React.createElement(Card, { label: T.identity }, /* @__PURE__ */ React.createElement(FieldRow, { label: T.userId, value: me.userId, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.carrierAddr, value: me.carrier, copy: true, qr: true }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.netKey, value: me.netKey, copy: true, last: true })), /* @__PURE__ */ React.createElement(Card, { label: T.network }, /* @__PURE__ */ React.createElement(FieldRow, { label: T.virtualIp, value: me.ip, copy: true }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.wireLabel, value: `${me.wire} \xB7 lossless`, mono: false }), /* @__PURE__ */ React.createElement(FieldRow, { label: T.version, value: `lan ${me.lanVer} \xB7 peer ${me.peerVer} \xB7 ${me.channel}`, last: true })), /* @__PURE__ */ React.createElement(Card, { label: T.dangerZone }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 13, padding: "14px 16px" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 34, height: 34, borderRadius: 8, flexShrink: 0, background: "color-mix(in oklab, var(--danger), transparent 86%)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--danger)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "trash", size: 17, stroke: 2 })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 13.5, fontWeight: 600, color: "var(--danger)" } }, T.deleteNode), /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--ui)", fontSize: 12, color: "var(--faint)", marginTop: 1 } }, T.deleteSub)), /* @__PURE__ */ React.createElement(Btn, { tone: "danger", size: "sm" }, T.delete)))));
1062
+ }
1063
+ Object.assign(window, { ProfileTab });
1064
+ function CallBtn({ icon, label, onClick, active, danger, wide }) {
1065
+ const bg = danger ? "var(--danger)" : active ? "var(--accent)" : "var(--chip)";
1066
+ const fg = danger || active ? "#fff" : "var(--text)";
1067
+ return /* @__PURE__ */ React.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement("button", { onClick, style: {
1068
+ width: wide ? 76 : 56,
1069
+ height: 56,
1070
+ borderRadius: wide ? 18 : 999,
1071
+ cursor: "pointer",
1072
+ background: bg,
1073
+ color: fg,
1074
+ border: "1px solid " + (active || danger ? "transparent" : "var(--line)"),
1075
+ display: "flex",
1076
+ alignItems: "center",
1077
+ justifyContent: "center"
1078
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 22, stroke: 2 })), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 10.5, color: "var(--faint)" } }, label));
1079
+ }
1080
+ function VideoTile({ peer, label, self, big }) {
1081
+ return /* @__PURE__ */ React.createElement("div", { style: {
1082
+ position: "relative",
1083
+ width: "100%",
1084
+ height: "100%",
1085
+ borderRadius: big ? 14 : 12,
1086
+ overflow: "hidden",
1087
+ background: "repeating-linear-gradient(45deg, var(--panel) 0 14px, var(--panel-2) 14px 28px)",
1088
+ border: "1px solid var(--line)",
1089
+ display: "flex",
1090
+ alignItems: "center",
1091
+ justifyContent: "center"
1092
+ } }, !self && /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId, size: big ? 92 : 44, radius: big ? 22 : 12 }), self && /* @__PURE__ */ React.createElement(Icon, { name: "userRound", size: big ? 60 : 30, color: "var(--faint)", stroke: 1.4 }), /* @__PURE__ */ React.createElement("span", { style: {
1093
+ position: "absolute",
1094
+ left: 10,
1095
+ bottom: 9,
1096
+ fontFamily: "var(--mono)",
1097
+ fontSize: 11,
1098
+ fontWeight: 600,
1099
+ color: "var(--dim)",
1100
+ background: "color-mix(in oklab, var(--bg), transparent 25%)",
1101
+ padding: "3px 8px",
1102
+ borderRadius: 6
1103
+ } }, label));
1104
+ }
1105
+ function CallOverlay({ T, peer, mode, onSetMode, onEnd }) {
1106
+ const [secs, setSecs] = React.useState(0);
1107
+ const [muted, setMuted] = React.useState(false);
1108
+ const [spk, setSpk] = React.useState(true);
1109
+ React.useEffect(() => {
1110
+ const id = setInterval(() => setSecs((s) => s + 1), 1e3);
1111
+ return () => clearInterval(id);
1112
+ }, []);
1113
+ const mm = String(Math.floor(secs / 60)).padStart(2, "0");
1114
+ const ss = String(secs % 60).padStart(2, "0");
1115
+ const video = mode === "video";
1116
+ const name = peer.alias || shortKey(peer.userId, 10, 6);
1117
+ const controls = /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 18, alignItems: "flex-start" } }, /* @__PURE__ */ React.createElement(CallBtn, { icon: muted ? "micFill" : "mic", label: muted ? T.unmute : T.mute, active: muted, onClick: () => setMuted((m) => !m) }), /* @__PURE__ */ React.createElement(CallBtn, { icon: "video", label: video ? T.audioOnly : T.toVideo, active: video, onClick: () => onSetMode(video ? "audio" : "video") }), /* @__PURE__ */ React.createElement(CallBtn, { icon: "speaker", label: T.speaker, active: spk, onClick: () => setSpk((s) => !s) }), /* @__PURE__ */ React.createElement(CallBtn, { icon: "phone", label: T.endCall, danger: true, wide: true, onClick: onEnd }));
1118
+ return /* @__PURE__ */ React.createElement("div", { style: { position: "fixed", inset: 0, zIndex: 70, background: "var(--bg)", display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 52, flexShrink: 0, display: "flex", alignItems: "center", gap: 10, padding: "0 18px", borderBottom: "1px solid var(--line)" } }, /* @__PURE__ */ React.createElement(Icon, { name: video ? "video" : "phone", size: 16, color: "var(--accent)", stroke: 2 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 13, fontWeight: 700, color: "var(--text)" } }, name), /* @__PURE__ */ React.createElement(Tag, { tone: peer.via === "relay" ? "warn" : "ok" }, T.connected, " \xB7 ", peer.via, " ", peer.ping, "ms"), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, color: "var(--text)", letterSpacing: 1 } }, mm, ":", ss)), video ? /* @__PURE__ */ React.createElement("div", { style: { flex: 1, position: "relative", padding: 18 } }, /* @__PURE__ */ React.createElement(VideoTile, { peer, label: "video \xB7 " + name, big: true }), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", right: 30, bottom: 30, width: 220, height: 148 } }, /* @__PURE__ */ React.createElement(VideoTile, { peer, label: T.you, self: true })), /* @__PURE__ */ React.createElement("div", { style: { position: "absolute", left: 0, right: 0, bottom: 30, display: "flex", justifyContent: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { padding: "16px 26px", borderRadius: 20, background: "color-mix(in oklab, var(--panel), transparent 8%)", border: "1px solid var(--line)" } }, controls))) : /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 22 } }, /* @__PURE__ */ React.createElement("div", { style: { position: "relative", display: "flex", alignItems: "center", justifyContent: "center" } }, /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", width: 150, height: 150, borderRadius: 999, border: "2px solid var(--accent)", animation: "dkpulse 2s ease-out infinite" } }), /* @__PURE__ */ React.createElement(DkIdenticon, { seed: peer.userId, size: 108, radius: 26 })), /* @__PURE__ */ React.createElement("div", { style: { textAlign: "center" } }, /* @__PURE__ */ React.createElement("div", { style: { fontFamily: "var(--mono)", fontSize: 22, fontWeight: 700, color: "var(--text)" } }, name), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8, justifyContent: "center", marginTop: 8 } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, color: "var(--faint)" } }, peer.ip), /* @__PURE__ */ React.createElement(Tag, { tone: peer.via === "relay" ? "warn" : "ok" }, peer.via, " \xB7 ", peer.ping, "ms"))), /* @__PURE__ */ React.createElement("div", { style: { marginTop: 18 } }, controls)));
1119
+ }
1120
+ Object.assign(window, { CallOverlay });
1121
+ const DK_DEFAULTS = (
1122
+ /*EDITMODE-BEGIN*/
1123
+ {
1124
+ "theme": "dark",
1125
+ "accent": "#7B6CF6",
1126
+ "density": "compact",
1127
+ "lang": "en",
1128
+ "startTab": "chat"
1129
+ }
1130
+ );
1131
+ function dkTheme(theme, accent) {
1132
+ const base = {
1133
+ "--ui": "'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace",
1134
+ "--mono": "'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace",
1135
+ "--accent": accent
1136
+ };
1137
+ if (theme === "light") return {
1138
+ ...base,
1139
+ "--ident-l": "0.92",
1140
+ "--bg": "oklch(0.965 0.004 264)",
1141
+ "--panel": "#ffffff",
1142
+ "--panel-2": "oklch(0.975 0.004 264)",
1143
+ "--line": "oklch(0.9 0.006 264)",
1144
+ "--text": "oklch(0.26 0.013 264)",
1145
+ "--dim": "oklch(0.5 0.013 264)",
1146
+ "--faint": "oklch(0.62 0.011 264)",
1147
+ "--chip": "oklch(0.945 0.006 264)",
1148
+ "--online": "oklch(0.62 0.16 150)",
1149
+ "--off": "oklch(0.8 0.01 264)",
1150
+ "--warn": "oklch(0.66 0.15 60)",
1151
+ "--danger": "oklch(0.58 0.21 25)",
1152
+ "--bub-me": accent,
1153
+ "--bub-them": "oklch(0.97 0.004 264)",
1154
+ "--rail": "oklch(0.99 0.003 264)"
1155
+ };
1156
+ return {
1157
+ ...base,
1158
+ "--ident-l": "0.32",
1159
+ "--bg": "oklch(0.165 0.012 264)",
1160
+ "--panel": "oklch(0.205 0.013 264)",
1161
+ "--panel-2": "oklch(0.235 0.014 264)",
1162
+ "--line": "oklch(0.295 0.014 264)",
1163
+ "--text": "oklch(0.93 0.008 264)",
1164
+ "--dim": "oklch(0.68 0.012 264)",
1165
+ "--faint": "oklch(0.52 0.013 264)",
1166
+ "--chip": "oklch(0.27 0.014 264)",
1167
+ "--online": "oklch(0.78 0.17 150)",
1168
+ "--off": "oklch(0.42 0.01 264)",
1169
+ "--warn": "oklch(0.78 0.15 70)",
1170
+ "--danger": "oklch(0.68 0.2 22)",
1171
+ "--bub-me": accent,
1172
+ "--bub-them": "oklch(0.255 0.014 264)",
1173
+ "--rail": "oklch(0.185 0.013 264)"
1174
+ };
1175
+ }
1176
+ const STR = {
1177
+ en: {
1178
+ chat: "Chat",
1179
+ network: "Network",
1180
+ profile: "Profile",
1181
+ addPlaceholder: "paste a friend's carrier address\u2026",
1182
+ add: "Add",
1183
+ search: "search peers / ip\u2026",
1184
+ requests: "Friend requests",
1185
+ accept: "accept",
1186
+ reject: "reject",
1187
+ peers: "Peers",
1188
+ call: "Voice call",
1189
+ alias: "Set alias",
1190
+ sendFile: "Send file",
1191
+ block: "Block peer",
1192
+ remove: "Remove friend",
1193
+ attach: "Attach file",
1194
+ message: "Message",
1195
+ send: "Send",
1196
+ pickPeer: "Select a peer to open the conversation",
1197
+ myIp: "my ip",
1198
+ copyAddr: "copy address",
1199
+ peersOnline: "peers online",
1200
+ direct: "direct (LAN)",
1201
+ activeEgress: "active egress",
1202
+ directEgress: "direct",
1203
+ noProxy: "no proxy",
1204
+ wireLabel: "wire",
1205
+ lossless: "lossless",
1206
+ reqs: "requests",
1207
+ pending: "pending inbound",
1208
+ peerRouting: "Peer routing",
1209
+ colPeer: "peer",
1210
+ colVip: "virtual ip",
1211
+ colPath: "path",
1212
+ colWire: "wire",
1213
+ openChat: "Open chat",
1214
+ ping: "Ping",
1215
+ exitNodes: "Exit nodes",
1216
+ addExit: "add exit",
1217
+ up: "up",
1218
+ routing: "routing",
1219
+ routeThru: "route",
1220
+ egressVia: "all egress routed via",
1221
+ stopRouting: "stop",
1222
+ identity: "Identity",
1223
+ userId: "user id",
1224
+ carrierAddr: "carrier address",
1225
+ netKey: "network key",
1226
+ virtualIp: "virtual ip",
1227
+ version: "version",
1228
+ editProfile: "edit",
1229
+ dangerZone: "Danger zone",
1230
+ deleteNode: "Delete this node",
1231
+ deleteSub: "Permanently remove identity & keys from this device",
1232
+ delete: "delete",
1233
+ settings: "Settings",
1234
+ call: "Call",
1235
+ connected: "connected",
1236
+ mute: "mute",
1237
+ unmute: "unmute",
1238
+ speaker: "speaker",
1239
+ toVideo: "video",
1240
+ audioOnly: "audio",
1241
+ endCall: "end",
1242
+ you: "you",
1243
+ meeting: "Meeting",
1244
+ meetingRooms: "Meeting rooms",
1245
+ nextVersion: "next version",
1246
+ createRoom: "New room",
1247
+ meetingDesc: "Spin up audio & video rooms across servers and invite peers by virtual IP. Group calls, screen share, and shared exit routing land in the next release."
1248
+ },
1249
+ zh: {
1250
+ chat: "\u804A\u5929",
1251
+ network: "\u7F51\u7EDC",
1252
+ profile: "\u6211\u7684",
1253
+ addPlaceholder: "\u7C98\u8D34\u597D\u53CB\u7684 carrier \u5730\u5740\u2026",
1254
+ add: "\u6DFB\u52A0",
1255
+ search: "\u641C\u7D22\u597D\u53CB / IP\u2026",
1256
+ requests: "\u597D\u53CB\u8BF7\u6C42",
1257
+ accept: "\u63A5\u53D7",
1258
+ reject: "\u62D2\u7EDD",
1259
+ peers: "\u597D\u53CB",
1260
+ call: "\u8BED\u97F3\u901A\u8BDD",
1261
+ alias: "\u8BBE\u7F6E\u5907\u6CE8",
1262
+ sendFile: "\u53D1\u9001\u6587\u4EF6",
1263
+ block: "\u62C9\u9ED1",
1264
+ remove: "\u5220\u9664\u597D\u53CB",
1265
+ attach: "\u9644\u4EF6",
1266
+ message: "\u6D88\u606F",
1267
+ send: "\u53D1\u9001",
1268
+ pickPeer: "\u9009\u62E9\u4E00\u4F4D\u597D\u53CB\u5F00\u59CB\u4F1A\u8BDD",
1269
+ myIp: "\u6211\u7684 IP",
1270
+ copyAddr: "\u590D\u5236\u5730\u5740",
1271
+ peersOnline: "\u5728\u7EBF\u597D\u53CB",
1272
+ direct: "\u76F4\u8FDE (\u5C40\u57DF\u7F51)",
1273
+ activeEgress: "\u5F53\u524D\u51FA\u53E3",
1274
+ directEgress: "\u76F4\u8FDE",
1275
+ noProxy: "\u65E0\u4EE3\u7406",
1276
+ wireLabel: "\u534F\u8BAE",
1277
+ lossless: "\u65E0\u635F",
1278
+ reqs: "\u8BF7\u6C42",
1279
+ pending: "\u5F85\u5904\u7406",
1280
+ peerRouting: "\u8282\u70B9\u8DEF\u7531",
1281
+ colPeer: "\u597D\u53CB",
1282
+ colVip: "\u865A\u62DF IP",
1283
+ colPath: "\u8DEF\u5F84",
1284
+ colWire: "\u534F\u8BAE",
1285
+ openChat: "\u6253\u5F00\u804A\u5929",
1286
+ ping: "Ping",
1287
+ exitNodes: "\u51FA\u53E3\u8282\u70B9",
1288
+ addExit: "\u6DFB\u52A0\u51FA\u53E3",
1289
+ up: "\u5728\u7EBF",
1290
+ routing: "\u4F7F\u7528\u4E2D",
1291
+ routeThru: "\u8DEF\u7531",
1292
+ egressVia: "\u5168\u90E8\u51FA\u53E3\u6D41\u91CF\u7ECF\u7531",
1293
+ stopRouting: "\u505C\u6B62",
1294
+ identity: "\u8EAB\u4EFD",
1295
+ userId: "\u7528\u6237 ID",
1296
+ carrierAddr: "Carrier \u5730\u5740",
1297
+ netKey: "\u7F51\u7EDC\u516C\u94A5",
1298
+ virtualIp: "\u865A\u62DF IP",
1299
+ version: "\u7248\u672C",
1300
+ editProfile: "\u7F16\u8F91",
1301
+ dangerZone: "\u5371\u9669\u64CD\u4F5C",
1302
+ deleteNode: "\u5220\u9664\u6B64\u8282\u70B9",
1303
+ deleteSub: "\u4ECE\u672C\u8BBE\u5907\u6C38\u4E45\u79FB\u9664\u8EAB\u4EFD\u4E0E\u5BC6\u94A5",
1304
+ delete: "\u5220\u9664",
1305
+ settings: "\u8BBE\u7F6E",
1306
+ call: "\u901A\u8BDD",
1307
+ connected: "\u5DF2\u8FDE\u63A5",
1308
+ mute: "\u9759\u97F3",
1309
+ unmute: "\u53D6\u6D88\u9759\u97F3",
1310
+ speaker: "\u626C\u58F0\u5668",
1311
+ toVideo: "\u89C6\u9891",
1312
+ audioOnly: "\u8BED\u97F3",
1313
+ endCall: "\u6302\u65AD",
1314
+ you: "\u6211",
1315
+ meeting: "\u4F1A\u8BAE",
1316
+ meetingRooms: "\u4F1A\u8BAE\u623F\u95F4",
1317
+ nextVersion: "\u4E0B\u4E2A\u7248\u672C",
1318
+ createRoom: "\u65B0\u5EFA\u623F\u95F4",
1319
+ meetingDesc: "\u8DE8\u670D\u52A1\u5668\u521B\u5EFA\u97F3\u89C6\u9891\u623F\u95F4\uFF0C\u901A\u8FC7\u865A\u62DF IP \u9080\u8BF7\u597D\u53CB\u3002\u7FA4\u7EC4\u901A\u8BDD\u3001\u5C4F\u5E55\u5171\u4EAB\u4E0E\u5171\u4EAB\u51FA\u53E3\u8DEF\u7531\u5C06\u5728\u4E0B\u4E2A\u7248\u672C\u4E0A\u7EBF\u3002"
1320
+ }
1321
+ };
1322
+ function RailBtn({ icon, label, active, soon, onClick }) {
1323
+ return /* @__PURE__ */ React.createElement("button", { onClick, title: label, style: {
1324
+ width: 52,
1325
+ height: 52,
1326
+ borderRadius: 11,
1327
+ cursor: "pointer",
1328
+ fontFamily: "var(--mono)",
1329
+ position: "relative",
1330
+ border: "1px solid " + (active ? "var(--line)" : "transparent"),
1331
+ background: active ? "var(--panel-2)" : "transparent",
1332
+ display: "flex",
1333
+ flexDirection: "column",
1334
+ alignItems: "center",
1335
+ justifyContent: "center",
1336
+ gap: 3
1337
+ } }, /* @__PURE__ */ React.createElement(Icon, { name: icon, size: 20, stroke: active ? 2.1 : 1.8, color: active ? "var(--accent)" : "var(--dim)" }), /* @__PURE__ */ React.createElement("span", { style: { fontSize: 9, fontWeight: 600, letterSpacing: 0.2, color: active ? "var(--accent)" : "var(--faint)" } }, label), soon && /* @__PURE__ */ React.createElement("span", { style: { position: "absolute", top: 5, right: 6, width: 6, height: 6, borderRadius: 999, background: "var(--warn)" } }));
1338
+ }
1339
+ function MeetingTab({ T, lang }) {
1340
+ return /* @__PURE__ */ React.createElement("div", { style: { flex: 1, display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 18, background: "var(--bg)", padding: 24 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 72, height: 72, borderRadius: 18, background: "var(--panel)", border: "1px solid var(--line)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--accent)" } }, /* @__PURE__ */ React.createElement(Icon, { name: "video", size: 32, stroke: 1.7 })), /* @__PURE__ */ React.createElement("div", { style: { textAlign: "center", maxWidth: 440 } }, /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 9, justifyContent: "center" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 19, fontWeight: 700, color: "var(--text)" } }, T.meetingRooms), /* @__PURE__ */ React.createElement(Tag, { tone: "warn" }, T.nextVersion)), /* @__PURE__ */ React.createElement("p", { style: { fontFamily: "var(--ui)", fontSize: 13, lineHeight: 1.55, color: "var(--dim)", marginTop: 10 } }, T.meetingDesc)), /* @__PURE__ */ React.createElement(Btn, { icon: "plus", style: { opacity: 0.5, pointerEvents: "none" } }, T.createRoom));
1341
+ }
1342
+ function DkApp() {
1343
+ const [t, setTweak] = useTweaks(DK_DEFAULTS);
1344
+ const [tab, setTab] = React.useState(t.startTab || "chat");
1345
+ const [activeId, setActiveId] = React.useState(null);
1346
+ const [call, setCall] = React.useState(null);
1347
+ const data = useDaemonData();
1348
+ const me = data.me;
1349
+ const peers = data.peers;
1350
+ const requests = data.requests;
1351
+ const exits = data.exits;
1352
+ const activeExit = data.activeExit;
1353
+ const T = STR[t.lang] || STR.en;
1354
+ const vars = dkTheme(t.theme, t.accent);
1355
+ const rowPad = t.density === "comfortable" ? "11px 12px" : "7px 10px";
1356
+ React.useEffect(() => {
1357
+ if (!activeId && peers.length) setActiveId(peers[0].id);
1358
+ }, [peers, activeId]);
1359
+ React.useEffect(() => {
1360
+ if (!activeId) return;
1361
+ data.loadThread(activeId);
1362
+ dkApi.markRead(activeId).then(data.refresh);
1363
+ }, [activeId]);
1364
+ const onSelect = (id) => setActiveId(id);
1365
+ const onAct = (id, kind) => {
1366
+ const r = requests.find((x) => x.id === id);
1367
+ const uid = r && r.userid || id;
1368
+ (kind === "accept" ? dkApi.accept(uid) : dkApi.reject(uid)).then(data.refresh);
1369
+ };
1370
+ const onRemove = (peer) => {
1371
+ dkApi.remove(peer.id).then(data.refresh);
1372
+ if (peer.id === activeId) setActiveId(null);
1373
+ };
1374
+ const onAdd = (address) => {
1375
+ if (address && address.trim()) dkApi.add(address.trim()).then(data.refresh);
1376
+ };
1377
+ const onSend = (text) => {
1378
+ if (!activeId || !text || !text.trim()) return;
1379
+ dkApi.send(activeId, text.trim()).then(() => data.loadThread(activeId)).then(data.refresh);
1380
+ };
1381
+ const onAlias = (peer) => {
1382
+ const a = window.prompt("Set alias for this peer (empty to clear):", peer.alias || "");
1383
+ if (a !== null) dkApi.alias(peer.id, a).then(data.refresh);
1384
+ };
1385
+ const onSetExit = () => {
1386
+ };
1387
+ const onOpenChat = (id) => {
1388
+ setActiveId(id);
1389
+ setTab("chat");
1390
+ };
1391
+ const onOpenNet = () => setTab("network");
1392
+ const nav = [
1393
+ { id: "chat", icon: "message", label: T.chat },
1394
+ { id: "network", icon: "network", label: T.network },
1395
+ { id: "meeting", icon: "video", label: T.meeting, soon: true },
1396
+ { id: "profile", icon: "userRound", label: T.profile }
1397
+ ];
1398
+ return /* @__PURE__ */ React.createElement("div", { style: { ...vars, "--row-pad": rowPad, position: "fixed", inset: 0, display: "flex", background: "var(--bg)", color: "var(--text)", fontFamily: "var(--ui)" } }, /* @__PURE__ */ React.createElement("div", { style: { width: 68, flexShrink: 0, borderRight: "1px solid var(--line)", background: "var(--rail)", display: "flex", flexDirection: "column", alignItems: "center", padding: "14px 0", gap: 8 } }, /* @__PURE__ */ React.createElement("div", { style: { width: 38, height: 38, borderRadius: 10, background: "var(--accent)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: 8 } }, /* @__PURE__ */ React.createElement(Icon, { name: "terminal", size: 20, color: "#fff", stroke: 2.2 })), nav.map((n) => /* @__PURE__ */ React.createElement(RailBtn, { key: n.id, icon: n.icon, label: n.label, active: tab === n.id, soon: n.soon, onClick: () => setTab(n.id) })), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement("div", { style: { position: "relative" } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 36, radius: 9 }))), /* @__PURE__ */ React.createElement("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column" } }, /* @__PURE__ */ React.createElement("div", { style: { height: 46, flexShrink: 0, borderBottom: "1px solid var(--line)", background: "var(--panel)", display: "flex", alignItems: "center", gap: 12, padding: "0 16px" } }, /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 14, fontWeight: 700, letterSpacing: -0.3, color: "var(--text)" } }, "decentlan"), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12, color: "var(--faint)" } }, "\xB7 ", nav.find((n) => n.id === tab).label.toLowerCase()), /* @__PURE__ */ React.createElement("div", { style: { flex: 1 } }), /* @__PURE__ */ React.createElement(Tag, { tone: "accent" }, me.channel, " \xB7 lan ", me.lanVer), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 7, padding: "0 4px" } }, /* @__PURE__ */ React.createElement(StatusDot, { online: me.online }), /* @__PURE__ */ React.createElement(Mono, { size: 12.5, copy: me.ip }, me.ip)), /* @__PURE__ */ React.createElement("span", { style: { width: 1, height: 22, background: "var(--line)" } }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", alignItems: "center", gap: 8 } }, /* @__PURE__ */ React.createElement(DkAvatar, { peer: { ...me, id: me.userId, agent: false }, size: 26, radius: 7 }), /* @__PURE__ */ React.createElement("span", { style: { fontFamily: "var(--mono)", fontSize: 12.5, fontWeight: 600, color: "var(--text)" } }, me.name))), tab === "chat" && /* @__PURE__ */ React.createElement(ChatTab, { T, lang: t.lang, peers, requests, activeId, thread: data.threads[activeId], onSelect, onAct, onAdd, onSend, onAlias, onRemove, onBlock: onRemove, onOpenNet, onCall: (p) => setCall({ peer: p, mode: "audio" }) }), tab === "network" && /* @__PURE__ */ React.createElement(NetworkTab, { T, me, peers, exits, activeExit, reqCount: requests.length, onSetExit, onOpenChat }), tab === "meeting" && /* @__PURE__ */ React.createElement(MeetingTab, { T, lang: t.lang }), tab === "profile" && /* @__PURE__ */ React.createElement(ProfileTab, { T, me })), call && /* @__PURE__ */ React.createElement(CallOverlay, { T, peer: call.peer, mode: call.mode, onSetMode: (m) => setCall((c) => ({ ...c, mode: m })), onEnd: () => setCall(null) }), /* @__PURE__ */ React.createElement(TweaksPanel, null, /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5916\u89C2" : "Appearance" }), /* @__PURE__ */ React.createElement(
1399
+ TweakRadio,
1400
+ {
1401
+ label: t.lang === "zh" ? "\u4E3B\u9898" : "Theme",
1402
+ value: t.theme,
1403
+ options: [{ value: "dark", label: t.lang === "zh" ? "\u6DF1\u8272" : "Dark" }, { value: "light", label: t.lang === "zh" ? "\u6D45\u8272" : "Light" }],
1404
+ onChange: (v) => setTweak("theme", v)
1405
+ }
1406
+ ), /* @__PURE__ */ React.createElement(
1407
+ TweakColor,
1408
+ {
1409
+ label: t.lang === "zh" ? "\u5F3A\u8C03\u8272" : "Accent",
1410
+ value: t.accent,
1411
+ options: ["#7B6CF6", "#3B82F6", "#10B981", "#F59E0B", "#EC4899", "#E0584B"],
1412
+ onChange: (v) => setTweak("accent", v)
1413
+ }
1414
+ ), /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u5E03\u5C40" : "Layout" }), /* @__PURE__ */ React.createElement(
1415
+ TweakRadio,
1416
+ {
1417
+ label: t.lang === "zh" ? "\u5BC6\u5EA6" : "Density",
1418
+ value: t.density,
1419
+ options: [{ value: "compact", label: t.lang === "zh" ? "\u7D27\u51D1" : "Compact" }, { value: "comfortable", label: t.lang === "zh" ? "\u5BBD\u677E" : "Comfy" }],
1420
+ onChange: (v) => setTweak("density", v)
1421
+ }
1422
+ ), /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u8BED\u8A00" : "Language" }), /* @__PURE__ */ React.createElement(
1423
+ TweakRadio,
1424
+ {
1425
+ label: t.lang === "zh" ? "\u754C\u9762\u8BED\u8A00" : "On-screen",
1426
+ value: t.lang,
1427
+ options: [{ value: "en", label: "EN" }, { value: "zh", label: "\u4E2D\u6587" }],
1428
+ onChange: (v) => setTweak("lang", v)
1429
+ }
1430
+ ), /* @__PURE__ */ React.createElement(TweakSection, { label: t.lang === "zh" ? "\u8DF3\u8F6C" : "Jump to" }), /* @__PURE__ */ React.createElement("div", { style: { display: "flex", gap: 6, flexWrap: "wrap" } }, nav.map((n) => /* @__PURE__ */ React.createElement("button", { key: n.id, onClick: () => setTab(n.id), style: {
1431
+ flex: "1 0 30%",
1432
+ height: 32,
1433
+ borderRadius: 8,
1434
+ cursor: "pointer",
1435
+ fontFamily: "var(--mono)",
1436
+ border: "1px solid rgba(255,255,255,0.14)",
1437
+ background: tab === n.id ? "rgba(255,255,255,0.18)" : "rgba(255,255,255,0.06)",
1438
+ color: "#fff",
1439
+ fontSize: 12,
1440
+ fontWeight: 600
1441
+ } }, n.label)))));
1442
+ }
1443
+ ReactDOM.createRoot(document.getElementById("root")).render(/* @__PURE__ */ React.createElement(DkApp, null));