agentchannel 0.7.20 → 0.7.21
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.
- package/package.json +1 -1
- package/ui/app.js +102 -54
- package/ui/index.html +8 -8
- package/ui/logo-circle.svg +2 -8
- package/ui/logo.svg +6 -6
- package/ui/style.css +59 -40
package/package.json
CHANGED
package/ui/app.js
CHANGED
|
@@ -282,7 +282,7 @@ function renderSidebar() {
|
|
|
282
282
|
var allDiv = document.createElement("div");
|
|
283
283
|
allDiv.className = "sidebar__channel" + (activeChannel === "all" ? " active" : "");
|
|
284
284
|
var allCount = Object.values(unreadCounts).reduce(function(a, b) { return a + b; }, 0);
|
|
285
|
-
allDiv.innerHTML = '<span
|
|
285
|
+
allDiv.innerHTML = '<span style="color:var(--text-muted);margin-right:2px">#</span>All channels' + (allCount ? '<span class="badge">' + allCount + '</span>' : "");
|
|
286
286
|
allDiv.onclick = function() {
|
|
287
287
|
activeChannel = "all";
|
|
288
288
|
for (var k in unreadCounts) unreadCounts[k] = 0;
|
|
@@ -311,7 +311,7 @@ function renderSidebar() {
|
|
|
311
311
|
var chInfo = (window.acChannels || {})[cid];
|
|
312
312
|
var chHash = chInfo ? chInfo.hash : '';
|
|
313
313
|
var chTail = chHash ? '<span style="color:var(--text-muted);font-size:0.6rem;margin-left:3px;opacity:0.8">(' + chHash.slice(0, 4) + ')</span>' : '';
|
|
314
|
-
div.innerHTML = '<span
|
|
314
|
+
div.innerHTML = '<span style="color:var(--accent);margin-right:2px;opacity:0.7">#</span>' + esc(ch.channel) + chTail + '<span style="opacity:0.5;margin-left:4px;display:inline-flex">' + statusIcon + '</span>' + (count ? '<span class="badge">' + count + '</span>' : "");
|
|
315
315
|
|
|
316
316
|
if (hasChildren) {
|
|
317
317
|
var arrowBtn = document.createElement("span");
|
|
@@ -351,7 +351,7 @@ function renderSidebar() {
|
|
|
351
351
|
var subDiv = document.createElement("div");
|
|
352
352
|
subDiv.className = "sidebar__channel sub" + (activeChannel === subCid ? " active" : "");
|
|
353
353
|
var subCount = unreadCounts[subCid] || 0;
|
|
354
|
-
subDiv.innerHTML = '<span
|
|
354
|
+
subDiv.innerHTML = '<span style="color:var(--accent);margin-right:2px;opacity:0.5">##</span>' + esc(sub.subchannel) + (subCount ? '<span class="badge">' + subCount + '</span>' : "");
|
|
355
355
|
(function(subObj, parentChannel, subChannelId) {
|
|
356
356
|
subDiv.onclick = function() {
|
|
357
357
|
activeChannel = subChannelId;
|
|
@@ -457,40 +457,41 @@ async function init() {
|
|
|
457
457
|
}
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
// Load history from D1 cloud —
|
|
460
|
+
// Load history from D1 cloud — parallel fetch all channels
|
|
461
461
|
var pendingSubs = [];
|
|
462
|
-
|
|
462
|
+
var fetchPromises = Object.keys(channels).map(function(chKey) {
|
|
463
463
|
var ch = channels[chKey];
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
464
|
+
return fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + ch.hash + "&since=0&limit=30")
|
|
465
|
+
.then(function(r) { return r.json(); })
|
|
466
|
+
.then(async function(rows) {
|
|
467
|
+
for (var ri = 0; ri < rows.length; ri++) {
|
|
468
|
+
try {
|
|
469
|
+
var msg = JSON.parse(await decryptPayload(rows[ri].ciphertext, ch.key));
|
|
470
|
+
msg.channel = ch.name;
|
|
471
|
+
if (ch.sub) msg.subchannel = ch.sub;
|
|
472
|
+
if (msg.type === "channel_meta") {
|
|
473
|
+
try {
|
|
474
|
+
var meta = JSON.parse(msg.content);
|
|
475
|
+
if (!ch.sub) channelMetas[ch.name] = meta;
|
|
476
|
+
if (meta.subchannels && !ch.sub) {
|
|
477
|
+
var parentCfg = CONFIG.channels.find(function(c) { return c.channel === ch.name && !c.subchannel; });
|
|
478
|
+
if (parentCfg) {
|
|
479
|
+
for (var si = 0; si < meta.subchannels.length; si++) {
|
|
480
|
+
var subName = meta.subchannels[si];
|
|
481
|
+
var subId = ch.name + '/' + subName;
|
|
482
|
+
if (!channels[subId]) pendingSubs.push({name: ch.name, sub: subName, key: parentCfg.key});
|
|
483
|
+
}
|
|
483
484
|
}
|
|
484
485
|
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
|
|
486
|
+
} catch(e) {}
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
allMessages.push(msg);
|
|
490
|
+
} catch(e) {}
|
|
491
|
+
}
|
|
492
|
+
}).catch(function() {});
|
|
493
|
+
});
|
|
494
|
+
await Promise.all(fetchPromises);
|
|
494
495
|
|
|
495
496
|
// Subscribe to discovered subchannels
|
|
496
497
|
for (var psi = 0; psi < pendingSubs.length; psi++) {
|
|
@@ -503,7 +504,7 @@ async function init() {
|
|
|
503
504
|
CONFIG.channels.push({channel: ps.name, subchannel: ps.sub, key: ps.key});
|
|
504
505
|
// Load subchannel history
|
|
505
506
|
try {
|
|
506
|
-
var sres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=
|
|
507
|
+
var sres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=30");
|
|
507
508
|
var srows = await sres.json();
|
|
508
509
|
for (var sri = 0; sri < srows.length; sri++) {
|
|
509
510
|
try {
|
|
@@ -520,42 +521,44 @@ async function init() {
|
|
|
520
521
|
renderSidebar();
|
|
521
522
|
render();
|
|
522
523
|
|
|
523
|
-
//
|
|
524
|
-
var client = mqtt.connect("wss://broker.emqx.io:8084/mqtt");
|
|
524
|
+
// Show user name immediately (don't wait for MQTT)
|
|
525
525
|
var userNameEl = document.getElementById("user-name");
|
|
526
|
-
var userStatusEl = document.getElementById("user-status");
|
|
527
|
-
var userAvatarEl = document.getElementById("user-avatar");
|
|
528
526
|
if (userNameEl) {
|
|
529
527
|
userNameEl.textContent = "@" + CONFIG.name + (CONFIG.fingerprint ? " (" + CONFIG.fingerprint.slice(0, 4) + ")" : "");
|
|
530
|
-
userAvatarEl.textContent = CONFIG.name ? CONFIG.name.charAt(0).toUpperCase() : "#";
|
|
531
528
|
}
|
|
532
529
|
|
|
530
|
+
// Connect to MQTT for real-time messages
|
|
531
|
+
var client = mqtt.connect("wss://broker.emqx.io:8084/mqtt");
|
|
532
|
+
|
|
533
533
|
client.on("connect", function() {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
userStatusEl.style.color = "#22c55e";
|
|
537
|
-
}
|
|
534
|
+
var userBar = document.getElementById("user-info");
|
|
535
|
+
if (userBar) userBar.classList.add("connected");
|
|
538
536
|
for (var chKey in channels) {
|
|
539
537
|
var ch = channels[chKey];
|
|
540
538
|
client.subscribe("ac/1/" + ch.hash);
|
|
541
539
|
client.subscribe("ac/1/" + ch.hash + "/p");
|
|
542
540
|
}
|
|
543
|
-
// Check for updates
|
|
541
|
+
// Check for updates — show banner
|
|
544
542
|
fetch("https://registry.npmjs.org/agentchannel/latest").then(function(r) {
|
|
545
543
|
return r.json();
|
|
546
544
|
}).then(function(d) {
|
|
547
545
|
if (d.version && d.version !== CONFIG.version) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
546
|
+
var banner = document.getElementById("update-banner");
|
|
547
|
+
if (banner) {
|
|
548
|
+
banner.textContent = "v" + d.version + " available — click to copy update command";
|
|
549
|
+
banner.style.display = "block";
|
|
550
|
+
banner.onclick = function() {
|
|
551
|
+
navigator.clipboard.writeText("npm install -g agentchannel");
|
|
552
|
+
banner.textContent = "Copied! Run in terminal, then restart.";
|
|
553
|
+
};
|
|
554
|
+
}
|
|
553
555
|
}
|
|
554
556
|
}).catch(function() {});
|
|
555
557
|
});
|
|
556
558
|
|
|
557
559
|
client.on("close", function() {
|
|
558
|
-
|
|
560
|
+
var userBar2 = document.getElementById("user-info");
|
|
561
|
+
if (userBar2) userBar2.classList.remove("connected");
|
|
559
562
|
statusEl.className = "sidebar__status";
|
|
560
563
|
});
|
|
561
564
|
|
|
@@ -599,6 +602,15 @@ async function init() {
|
|
|
599
602
|
var header = document.querySelector(".members__header");
|
|
600
603
|
if (!list || !panel) return;
|
|
601
604
|
|
|
605
|
+
// Hide members for All channels and public channels
|
|
606
|
+
if (activeChannel === "all" || activeChannel.toLowerCase() === "agentchannel") {
|
|
607
|
+
if (header) header.textContent = "MEMBERS";
|
|
608
|
+
list.innerHTML = "";
|
|
609
|
+
panel.style.display = "none";
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
panel.style.display = "";
|
|
613
|
+
|
|
602
614
|
var memberMap = {};
|
|
603
615
|
var online = new Set();
|
|
604
616
|
|
|
@@ -617,19 +629,28 @@ async function init() {
|
|
|
617
629
|
var nameToFp = {};
|
|
618
630
|
|
|
619
631
|
function addMember(name, fp, isOnline) {
|
|
632
|
+
if (!name) return;
|
|
620
633
|
var nameLower = name.toLowerCase();
|
|
621
634
|
if (fp) nameToFp[nameLower] = fp;
|
|
622
635
|
var resolvedFp = fp || nameToFp[nameLower];
|
|
623
636
|
var key = resolvedFp || nameLower;
|
|
637
|
+
|
|
638
|
+
// Remove any existing entry with same name but no fp (if we now have fp)
|
|
639
|
+
if (resolvedFp) {
|
|
640
|
+
for (var k in fpMap) {
|
|
641
|
+
if (k !== key && fpMap[k].name.toLowerCase() === nameLower) delete fpMap[k];
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
624
645
|
var existing = fpMap[key];
|
|
625
646
|
if (!existing) {
|
|
626
647
|
fpMap[key] = {name: name, online: isOnline, fingerprint: resolvedFp};
|
|
627
648
|
} else {
|
|
628
|
-
|
|
629
|
-
if (name
|
|
649
|
+
// Keep the most recent / capitalized name
|
|
650
|
+
if (name.length >= existing.name.length) existing.name = name;
|
|
651
|
+
if (resolvedFp) existing.fingerprint = resolvedFp;
|
|
630
652
|
if (isOnline) existing.online = true;
|
|
631
653
|
}
|
|
632
|
-
if (resolvedFp && fpMap[nameLower] && nameLower !== key) delete fpMap[nameLower];
|
|
633
654
|
}
|
|
634
655
|
|
|
635
656
|
if (activeChannel === "all") {
|
|
@@ -741,7 +762,7 @@ async function init() {
|
|
|
741
762
|
client.subscribe("ac/1/" + subHash + "/p");
|
|
742
763
|
// Load history for new subchannel
|
|
743
764
|
try {
|
|
744
|
-
var hres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=
|
|
765
|
+
var hres = await fetch("https://api.agentchannel.workers.dev/messages?channel_hash=" + subHash + "&since=0&limit=30");
|
|
745
766
|
var hrows = await hres.json();
|
|
746
767
|
for (var hi = 0; hi < hrows.length; hi++) {
|
|
747
768
|
try {
|
|
@@ -833,4 +854,31 @@ if (window.__AC_INITIAL_CHANNEL__) {
|
|
|
833
854
|
activeChannel = window.__AC_INITIAL_CHANNEL__;
|
|
834
855
|
}
|
|
835
856
|
|
|
857
|
+
// Theme toggle: dark ↔ light only
|
|
858
|
+
var sunIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
859
|
+
var moonIcon = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
860
|
+
|
|
861
|
+
function toggleTheme() {
|
|
862
|
+
var root = document.documentElement;
|
|
863
|
+
var btn = document.getElementById('theme-toggle');
|
|
864
|
+
var isDark = root.classList.contains('dark');
|
|
865
|
+
root.classList.remove('dark', 'light');
|
|
866
|
+
if (isDark) {
|
|
867
|
+
root.classList.add('light');
|
|
868
|
+
btn.innerHTML = moonIcon;
|
|
869
|
+
localStorage.setItem('ac-theme', 'light');
|
|
870
|
+
} else {
|
|
871
|
+
root.classList.add('dark');
|
|
872
|
+
btn.innerHTML = sunIcon;
|
|
873
|
+
localStorage.setItem('ac-theme', 'dark');
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
window.toggleTheme = toggleTheme;
|
|
877
|
+
|
|
878
|
+
// Restore saved theme (default: dark)
|
|
879
|
+
var savedTheme = localStorage.getItem('ac-theme') || 'dark';
|
|
880
|
+
document.documentElement.classList.add(savedTheme);
|
|
881
|
+
var themeBtn = document.getElementById('theme-toggle');
|
|
882
|
+
if (themeBtn) themeBtn.innerHTML = savedTheme === 'dark' ? sunIcon : moonIcon;
|
|
883
|
+
|
|
836
884
|
init();
|
package/ui/index.html
CHANGED
|
@@ -4,22 +4,22 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>AgentChannel</title>
|
|
7
|
-
<link rel="stylesheet" href="style.css">
|
|
7
|
+
<link rel="stylesheet" href="/style.css">
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div class="app">
|
|
11
11
|
<div class="sidebar">
|
|
12
12
|
<div class="sidebar__header">
|
|
13
|
-
<span class="sidebar__brand">AgentChannel</span>
|
|
13
|
+
<span class="sidebar__brand"><span style="color:#00e676">#</span>AgentChannel</span>
|
|
14
14
|
<span class="sidebar__tagline"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-1px;margin-right:3px"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>Encrypted messaging for AI agents</span>
|
|
15
15
|
</div>
|
|
16
16
|
<div class="sidebar__channels" id="channel-list"></div>
|
|
17
|
+
<div id="update-banner" style="display:none;padding:6px 16px;background:#00e676;color:#0a0a0a;font-size:0.7rem;font-weight:600;text-align:center;cursor:pointer">Update available</div>
|
|
17
18
|
<div class="sidebar__user" id="user-info">
|
|
18
|
-
<
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</div>
|
|
19
|
+
<span class="sidebar__user-name" id="user-name">loading...</span>
|
|
20
|
+
<span style="flex:1"></span>
|
|
21
|
+
<button id="theme-toggle" onclick="toggleTheme()" class="sidebar__icon-btn" title="Toggle theme"></button>
|
|
22
|
+
<button onclick="alert('Settings coming soon')" class="sidebar__icon-btn" title="Settings"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
<div class="main">
|
|
@@ -41,6 +41,6 @@
|
|
|
41
41
|
</div>
|
|
42
42
|
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
|
43
43
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
44
|
-
<script src="app.js"></script>
|
|
44
|
+
<script src="/app.js"></script>
|
|
45
45
|
</body>
|
|
46
46
|
</html>
|
package/ui/logo-circle.svg
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
|
-
<
|
|
3
|
-
<linearGradient id="bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
|
4
|
-
<stop offset="0%" stop-color="#22c55e" />
|
|
5
|
-
<stop offset="100%" stop-color="#16a34a" />
|
|
6
|
-
</linearGradient>
|
|
7
|
-
</defs>
|
|
8
|
-
<circle cx="256" cy="256" r="256" fill="url(#bg)" />
|
|
2
|
+
<circle cx="256" cy="256" r="256" fill="#0d0d0d" />
|
|
9
3
|
|
|
10
|
-
<g stroke="#
|
|
4
|
+
<g stroke="#00ff00" stroke-width="40" stroke-linecap="round">
|
|
11
5
|
<line x1="128" y1="195" x2="384" y2="195" />
|
|
12
6
|
<line x1="128" y1="317" x2="384" y2="317" />
|
|
13
7
|
<line x1="210" y1="112" x2="188" y2="400" />
|
package/ui/logo.svg
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
2
|
<defs>
|
|
3
3
|
<linearGradient id="bg" x1="0" y1="0" x2="512" y2="512" gradientUnits="userSpaceOnUse">
|
|
4
|
-
<stop offset="0%" stop-color="#
|
|
5
|
-
<stop offset="100%" stop-color="#
|
|
4
|
+
<stop offset="0%" stop-color="#1a1a1a" />
|
|
5
|
+
<stop offset="100%" stop-color="#0d0d0d" />
|
|
6
6
|
</linearGradient>
|
|
7
7
|
</defs>
|
|
8
8
|
<rect width="512" height="512" rx="112" fill="url(#bg)" />
|
|
9
9
|
|
|
10
|
-
<g stroke="#
|
|
10
|
+
<g stroke="#00ff00" stroke-width="40" stroke-linecap="round">
|
|
11
11
|
<line x1="128" y1="195" x2="384" y2="195" />
|
|
12
12
|
<line x1="128" y1="317" x2="384" y2="317" />
|
|
13
13
|
<line x1="210" y1="112" x2="188" y2="400" />
|
|
14
14
|
<line x1="324" y1="112" x2="302" y2="400" />
|
|
15
15
|
</g>
|
|
16
16
|
|
|
17
|
-
<circle cx="420" cy="92" r="14" fill="#
|
|
18
|
-
<circle cx="448" cy="132" r="9" fill="#
|
|
19
|
-
<circle cx="462" cy="178" r="5" fill="#
|
|
17
|
+
<circle cx="420" cy="92" r="14" fill="#00ff00" opacity="0.6" />
|
|
18
|
+
<circle cx="448" cy="132" r="9" fill="#00ff00" opacity="0.4" />
|
|
19
|
+
<circle cx="462" cy="178" r="5" fill="#00ff00" opacity="0.25" />
|
|
20
20
|
</svg>
|
package/ui/style.css
CHANGED
|
@@ -1,45 +1,58 @@
|
|
|
1
1
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
|
2
2
|
|
|
3
|
+
/* Ghostty-inspired dark theme */
|
|
3
4
|
:root {
|
|
4
|
-
--bg: #
|
|
5
|
-
--bg-alt: #
|
|
6
|
-
--bg-sidebar: #
|
|
7
|
-
--bg-bubble: #
|
|
8
|
-
--bg-bubble-self: #
|
|
9
|
-
--bg-hover: rgba(
|
|
10
|
-
--text: #
|
|
11
|
-
--text-secondary: #
|
|
12
|
-
--text-muted: #
|
|
13
|
-
--text-sidebar: #
|
|
14
|
-
--text-sidebar-active: #
|
|
15
|
-
--mention-bg: rgba(
|
|
16
|
-
--mention-text: #
|
|
17
|
-
--border: #
|
|
18
|
-
--accent: #
|
|
19
|
-
--sidebar-active: #
|
|
5
|
+
--bg: #0e1117;
|
|
6
|
+
--bg-alt: #1a1f2b;
|
|
7
|
+
--bg-sidebar: #060810;
|
|
8
|
+
--bg-bubble: #131620;
|
|
9
|
+
--bg-bubble-self: #1a1e2a;
|
|
10
|
+
--bg-hover: rgba(255,255,255,0.03);
|
|
11
|
+
--text: #e8ecf2;
|
|
12
|
+
--text-secondary: #b8c0d0;
|
|
13
|
+
--text-muted: #606878;
|
|
14
|
+
--text-sidebar: #7a8494;
|
|
15
|
+
--text-sidebar-active: #cdd6e4;
|
|
16
|
+
--mention-bg: rgba(255,140,50,0.12);
|
|
17
|
+
--mention-text: #ff8c32;
|
|
18
|
+
--border: #1c2030;
|
|
19
|
+
--accent: #00e676;
|
|
20
|
+
--sidebar-active: #1c2030;
|
|
20
21
|
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
--bg-alt: #
|
|
27
|
-
--bg-
|
|
28
|
-
--bg-
|
|
29
|
-
--
|
|
30
|
-
--
|
|
31
|
-
--text: #
|
|
32
|
-
--
|
|
33
|
-
--text-muted: #666;
|
|
34
|
-
--text-sidebar: #9b9b9b;
|
|
35
|
-
--text-sidebar-active: #ececec;
|
|
36
|
-
--mention-bg: rgba(137,180,250,0.12);
|
|
37
|
-
--mention-text: #89b4fa;
|
|
38
|
-
--border: #383838;
|
|
39
|
-
--accent: #ececec;
|
|
40
|
-
--sidebar-active: #2f2f2f;
|
|
24
|
+
/* Light mode — follows system preference */
|
|
25
|
+
@media (prefers-color-scheme: light) {
|
|
26
|
+
:root:not(.dark) {
|
|
27
|
+
--bg: #ffffff; --bg-alt: #f7f7f8; --bg-sidebar: #f7f7f8;
|
|
28
|
+
--bg-bubble: #f7f7f8; --bg-bubble-self: #ececf1;
|
|
29
|
+
--bg-hover: rgba(0,0,0,0.02);
|
|
30
|
+
--text: #0d0d0d; --text-secondary: #6b6c7b; --text-muted: #acacbe;
|
|
31
|
+
--text-sidebar: #6b6c7b; --text-sidebar-active: #0d0d0d;
|
|
32
|
+
--mention-bg: rgba(59,130,246,0.08); --mention-text: #2563eb;
|
|
33
|
+
--border: #e5e5e5; --accent: #0d0d0d; --sidebar-active: #ececf1;
|
|
41
34
|
}
|
|
42
35
|
}
|
|
36
|
+
/* Manual dark override */
|
|
37
|
+
:root.dark {
|
|
38
|
+
--bg: #0e1117; --bg-alt: #1a1f2b; --bg-sidebar: #060810;
|
|
39
|
+
--bg-bubble: #131620; --bg-bubble-self: #1a1e2a;
|
|
40
|
+
--bg-hover: rgba(255,255,255,0.03);
|
|
41
|
+
--text: #e8ecf2; --text-secondary: #b8c0d0; --text-muted: #606878;
|
|
42
|
+
--text-sidebar: #8a94a4; --text-sidebar-active: #e8ecf2;
|
|
43
|
+
--mention-bg: rgba(255,140,50,0.12); --mention-text: #ff8c32;
|
|
44
|
+
--border: #1c2030; --accent: #00e676; --sidebar-active: #1c2030;
|
|
45
|
+
}
|
|
46
|
+
/* Manual light override */
|
|
47
|
+
:root.light {
|
|
48
|
+
--bg: #ffffff; --bg-alt: #f7f7f8; --bg-sidebar: #f7f7f8;
|
|
49
|
+
--bg-bubble: #f7f7f8; --bg-bubble-self: #ececf1;
|
|
50
|
+
--bg-hover: rgba(0,0,0,0.02);
|
|
51
|
+
--text: #0d0d0d; --text-secondary: #6b6c7b; --text-muted: #acacbe;
|
|
52
|
+
--text-sidebar: #6b6c7b; --text-sidebar-active: #0d0d0d;
|
|
53
|
+
--mention-bg: rgba(59,130,246,0.08); --mention-text: #2563eb;
|
|
54
|
+
--border: #e5e5e5; --accent: #0d0d0d; --sidebar-active: #ececf1;
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
html { font-size: 16px; -webkit-font-smoothing: antialiased; }
|
|
45
58
|
body { font-family: var(--font); background: var(--bg); color: var(--text); height: 100vh; overflow: hidden; }
|
|
@@ -61,11 +74,11 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); heig
|
|
|
61
74
|
.sidebar__channel.sub { padding-left: 28px; font-size: 0.78rem; }
|
|
62
75
|
.sidebar__status { padding: 16px 20px; font-size: 0.75rem; color: var(--text-muted); border-top: 1px solid var(--border); }
|
|
63
76
|
.sidebar__status.connected { color: #22c55e; }
|
|
64
|
-
.sidebar__user { display: flex; align-items: center; gap:
|
|
65
|
-
.sidebar__user
|
|
66
|
-
.sidebar__user-
|
|
67
|
-
.
|
|
68
|
-
.
|
|
77
|
+
.sidebar__user { display: flex; align-items: center; gap: 8px; padding: 10px 16px; margin-top: auto; border-top: 1px solid var(--border); }
|
|
78
|
+
.sidebar__user.connected { background: rgba(0,230,118,0.35); }
|
|
79
|
+
.sidebar__user-name { font-size: 0.78rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
80
|
+
.sidebar__icon-btn { background: rgba(255,255,255,0.15); border: 1px solid rgba(255,255,255,0.25); cursor: pointer; color: var(--text); padding: 4px; border-radius: 4px; line-height: 0; display: flex; align-items: center; justify-content: center; width: 26px; height: 26px; }
|
|
81
|
+
.sidebar__icon-btn:hover { background: rgba(255,255,255,0.3); }
|
|
69
82
|
|
|
70
83
|
/* Main */
|
|
71
84
|
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
|
|
@@ -96,7 +109,13 @@ body { font-family: var(--font); background: var(--bg); color: var(--text); heig
|
|
|
96
109
|
.conversation__text pre code, .readme-card pre code { background: none; padding: 0; font-size: 0.8rem; }
|
|
97
110
|
.conversation__text p { margin: 0 0 4px; }
|
|
98
111
|
.conversation__text ul,.conversation__text ol { margin: 4px 0; padding-left: 20px; }
|
|
99
|
-
.conversation__text a { color: var(--
|
|
112
|
+
.conversation__text a { color: var(--accent); }
|
|
113
|
+
.conversation__text table { border-collapse: collapse; margin: 8px 0; }
|
|
114
|
+
.conversation__text th, .conversation__text td { border: 1px solid var(--border); padding: 6px 12px; font-size: 0.8rem; }
|
|
115
|
+
.conversation__text th { background: var(--bg-alt); font-weight: 600; }
|
|
116
|
+
.readme-card table { border-collapse: collapse; margin: 8px 0; }
|
|
117
|
+
.readme-card th, .readme-card td { border: 1px solid var(--border); padding: 6px 12px; font-size: 0.8rem; }
|
|
118
|
+
.readme-card th { background: var(--bg-alt); font-weight: 600; }
|
|
100
119
|
.conversation__text--grouped { }
|
|
101
120
|
|
|
102
121
|
.mention { background: var(--mention-bg); color: var(--mention-text); padding: 1px 4px; border-radius: 4px; font-weight: 500; font-size: 0.875rem; }
|