UI: Font Awesome icons, Material touches, 3-line collapse button

Swap all emoji icons for Font Awesome (sidebar, homepage tiles, profile/chat/
list/settings/relay actions); add Roboto font + card elevation hover for a
Material feel. Replace the bottom 'Collapse' pill with a 3-line (fa-bars) toggle
in the sidebar header; remove the old collapse pill CSS/JS. Copy buttons toggle
FA check/copy via innerHTML. Plain text status (✓/✗) and back arrows kept.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-06-05 20:06:59 +01:00
parent 1881b74d92
commit 4ed2f9ba14
7 changed files with 74 additions and 72 deletions

View File

@@ -12,6 +12,9 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}SimpleX Manager{% endblock %}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.min.js"></script>
<script>
document.addEventListener('htmx:configRequest', function(evt) {
@@ -76,9 +79,16 @@
--badge-red-text: #ff6b6b;
}
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
body { font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
background: var(--bg); color: var(--text); min-height: 100vh; }
/* Material-ish elevation: cards lift slightly on hover */
.card { transition: box-shadow 0.18s ease, transform 0.05s ease; }
.card:hover { box-shadow: 0 4px 18px rgba(0,0,0,0.14); }
[data-theme="matrix"] .card:hover { box-shadow: 0 0 18px rgba(0,255,65,0.22); }
.btn { box-shadow: 0 1px 3px rgba(0,0,0,0.18); letter-spacing: 0.2px; }
.btn-ghost { box-shadow: none; }
[data-theme="matrix"] body {
font-family: 'SF Mono', 'Consolas', 'Courier New', monospace;
text-shadow: 0 0 2px rgba(0,255,65,0.4);
@@ -98,13 +108,21 @@
}
html.collapsed .sidebar { width: 64px; }
.side-head { display: flex; align-items: center; border-bottom: 1px solid var(--border); }
.collapse-toggle {
flex-shrink: 0; background: none; border: none; cursor: pointer;
color: var(--muted); font-size: 17px; padding: 18px;
}
.collapse-toggle:hover { color: var(--text); }
.nav-brand {
display: flex; align-items: center; gap: 10px;
padding: 18px; font-size: 16px; font-weight: 700;
padding: 18px 18px 18px 0; font-size: 16px; font-weight: 700;
color: var(--accent); text-decoration: none;
border-bottom: 1px solid var(--border); white-space: nowrap; overflow: hidden;
white-space: nowrap; overflow: hidden;
}
.nav-brand .brand-icon { font-size: 18px; flex-shrink: 0; }
html.collapsed .nav-brand { display: none; }
html.collapsed .side-head { justify-content: center; }
.side-nav { display: flex; flex-direction: column; padding: 8px 0; }
.side-nav a {
@@ -139,17 +157,6 @@
.ss-dot.online { background: var(--green); box-shadow: 0 0 5px var(--green); }
.ss-dot.offline { background: var(--red); }
html.collapsed .side-status { display: none; }
.collapse-btn {
display: flex; align-items: center; justify-content: space-between; gap: 10px;
margin: 8px 12px; padding: 8px 12px; border-radius: 8px;
background: var(--bg); border: 1px solid var(--border); cursor: pointer;
color: var(--muted); font-family: inherit; font-size: 12px; font-weight: 600;
white-space: nowrap; overflow: hidden;
transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.collapse-btn:hover { color: var(--text); border-color: var(--accent); }
.collapse-btn .ico { font-size: 16px; line-height: 1; flex-shrink: 0; }
html.collapsed .collapse-btn { justify-content: center; margin: 8px; padding: 8px; }
html.collapsed .lbl, html.collapsed .brand-text { display: none; }
@@ -183,10 +190,12 @@
}
html.collapsed .sidebar { width: 240px; } /* ignore collapse on mobile */
html.collapsed .lbl, html.collapsed .brand-text { display: inline; }
html.collapsed .nav-brand { display: flex; } /* keep brand visible on mobile */
html.collapsed .side-head { justify-content: flex-start; }
body.sidebar-open .sidebar { transform: translateX(0); }
body.sidebar-open .backdrop { display: block; }
.mobile-menu-btn { display: flex; }
.collapse-btn { display: none; }
.collapse-toggle { display: none; } /* mobile uses the drawer toggle instead */
.container { padding-top: 64px; }
}
@@ -262,41 +271,41 @@
{% block head %}{% endblock %}
</head>
<body>
<button class="mobile-menu-btn" onclick="toggleSidebar()" aria-label="Menu"></button>
<button class="mobile-menu-btn" onclick="toggleSidebar()" aria-label="Menu"><i class="fa-solid fa-bars"></i></button>
<div class="app">
<aside class="sidebar" id="sidebar">
<a class="nav-brand" href="/">
<span class="brand-icon"></span><span class="brand-text">SimpleX Manager</span>
</a>
<div class="side-head">
<button class="collapse-toggle" onclick="toggleCollapse()" title="Collapse sidebar" aria-label="Collapse sidebar"><i class="fa-solid fa-bars"></i></button>
<a class="nav-brand" href="/">
<span class="brand-icon"><i class="fa-solid fa-diamond"></i></span><span class="brand-text">SimpleX Manager</span>
</a>
</div>
<nav class="side-nav">
<!-- Group 1: accounts -->
<a href="/users" {% if nav_active == 'users' %}class="active"{% endif %}><span class="ico">👤</span><span class="lbl">Users</span></a>
<a href="/businesses" {% if nav_active == 'businesses' %}class="active"{% endif %}><span class="ico">💼</span><span class="lbl">Businesses</span></a>
<a href="/bots" {% if nav_active == 'bots' %}class="active"{% endif %}><span class="ico">🤖</span><span class="lbl">Bots</span></a>
<a href="https://simplex.chat/file/" target="_blank" rel="noopener"><span class="ico">📁</span><span class="lbl">File Upload</span></a>
<a href="/users" {% if nav_active == 'users' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-user"></i></span><span class="lbl">Users</span></a>
<a href="/businesses" {% if nav_active == 'businesses' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-briefcase"></i></span><span class="lbl">Businesses</span></a>
<a href="/bots" {% if nav_active == 'bots' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-robot"></i></span><span class="lbl">Bots</span></a>
<a href="https://simplex.chat/file/" target="_blank" rel="noopener"><span class="ico"><i class="fa-solid fa-upload"></i></span><span class="lbl">File Upload</span></a>
<!-- Group 2: relays -->
<a href="/relays/chat" class="nav-sep {% if nav_active == 'relays' and kind == 'chat' %}active{% endif %}"><span class="ico">💬</span><span class="lbl">Chat Relay</span></a>
<a href="/relays/file" {% if nav_active == 'relays' and kind == 'file' %}class="active"{% endif %}><span class="ico">📤</span><span class="lbl">File Relay</span></a>
<a href="/relays/message" {% if nav_active == 'relays' and kind == 'message' %}class="active"{% endif %}><span class="ico">✉️</span><span class="lbl">Message Relay</span></a>
<a href="/relays/chat" class="nav-sep {% if nav_active == 'relays' and kind == 'chat' %}active{% endif %}"><span class="ico"><i class="fa-solid fa-comments"></i></span><span class="lbl">Chat Relay</span></a>
<a href="/relays/file" {% if nav_active == 'relays' and kind == 'file' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-file-export"></i></span><span class="lbl">File Relay</span></a>
<a href="/relays/message" {% if nav_active == 'relays' and kind == 'message' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-envelope"></i></span><span class="lbl">Message Relay</span></a>
<!-- Group 3: system -->
<a href="/network" class="nav-sep {% if nav_active == 'network' %}active{% endif %}"><span class="ico">📡</span><span class="lbl">Network</span></a>
<a href="/notifications" {% if nav_active == 'notifications' %}class="active"{% endif %}><span class="ico">🔔</span><span class="lbl">Notifications</span><span class="notif-badge" id="notif-badge" style="display:none;"></span></a>
<a href="/settings" {% if nav_active == 'settings' %}class="active"{% endif %}><span class="ico">⚙️</span><span class="lbl">Settings</span></a>
<a href="/network" class="nav-sep {% if nav_active == 'network' %}active{% endif %}"><span class="ico"><i class="fa-solid fa-tower-broadcast"></i></span><span class="lbl">Network</span></a>
<a href="/notifications" {% if nav_active == 'notifications' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-bell"></i></span><span class="lbl">Notifications</span><span class="notif-badge" id="notif-badge" style="display:none;"></span></a>
<a href="/settings" {% if nav_active == 'settings' %}class="active"{% endif %}><span class="ico"><i class="fa-solid fa-gear"></i></span><span class="lbl">Settings</span></a>
<!-- Group 4: external -->
<a href="https://simplex.chat/downloads/" target="_blank" rel="noopener" class="nav-sep"><span class="ico">📲</span><span class="lbl">Get App</span></a>
<a href="https://simplex.chat/downloads/" target="_blank" rel="noopener" class="nav-sep"><span class="ico"><i class="fa-solid fa-download"></i></span><span class="lbl">Get App</span></a>
</nav>
<div class="side-foot">
<a href="/network" class="side-status" id="side-status" title="View SimpleX network &amp; servers"
style="display:block;text-decoration:none;{% if nav_active == 'network' %}background:var(--bg);{% endif %}">
<div class="ss-row"><span class="ss-dot" id="ss-dot"></span><span id="ss-running">/</span>&nbsp;running</div>
<div class="ss-row" id="ss-servers">📡 </div>
<div class="ss-row"><i class="fa-solid fa-server" style="width:14px;text-align:center;"></i>&nbsp;<span id="ss-servers"></span></div>
<div class="ss-row" id="ss-ops" style="opacity:0.8;"></div>
</a>
<button class="collapse-btn" onclick="toggleCollapse()" title="Collapse sidebar" aria-label="Collapse sidebar">
<span class="lbl">Collapse</span><span class="ico" id="collapse-ico"></span>
</button>
<nav class="side-nav">
<a href="/logout"><span class="ico"></span><span class="lbl">Logout</span></a>
<a href="/logout"><span class="ico"><i class="fa-solid fa-right-from-bracket"></i></span><span class="lbl">Logout</span></a>
</nav>
</div>
</aside>
@@ -320,14 +329,7 @@
function toggleCollapse() {
const collapsed = document.documentElement.classList.toggle('collapsed');
localStorage.setItem('sidebar-collapsed', collapsed ? '1' : '');
const ico = document.getElementById('collapse-ico');
if (ico) ico.textContent = collapsed ? '' : '';
}
// Sync collapse icon with restored state on load
(function(){
const ico = document.getElementById('collapse-ico');
if (ico && document.documentElement.classList.contains('collapsed')) ico.textContent = '';
})();
// Poll for unread notifications and update the sidebar badge
async function pollNotifications() {
@@ -358,7 +360,7 @@
const dot = document.getElementById('ss-dot');
dot.className = 'ss-dot ' + (d.online ? 'online' : 'offline');
document.getElementById('ss-servers').textContent =
d.online ? `📡 ${d.smp_servers} SMP · ${d.xftp_servers} XFTP` : 'no profile running';
d.online ? `${d.smp_servers} SMP · ${d.xftp_servers} XFTP` : 'no profile running';
document.getElementById('ss-ops').textContent =
(d.operators && d.operators.length) ? d.operators.join(', ') : '';
} catch (e) {}

View File

@@ -51,11 +51,11 @@
<div class="chat-head">
<span class="title">{{ chat_name }}</span>
<button class="btn btn-ghost" style="padding:4px 12px;font-size:12px;"
onclick="loadMessages(true)"> Refresh</button>
onclick="loadMessages(true)"><i class="fa-solid fa-rotate-right"></i> Refresh</button>
</div>
{% if is_channel %}
<div class="chat-banner">📢 Channel — messages you send here broadcast to all subscribers.</div>
<div class="chat-banner"><i class="fa-solid fa-bullhorn"></i> Channel — messages you send here broadcast to all subscribers.</div>
{% endif %}
<div class="chat-log" id="chat-log">

View File

@@ -33,19 +33,19 @@
<!-- Area 1: your accounts -->
<div class="tiles">
<a class="tile" href="/users">
<span class="t-ico">👤</span>
<span class="t-ico"><i class="fa-solid fa-user"></i></span>
<span class="t-title">Users</span>
</a>
<a class="tile" href="/businesses">
<span class="t-ico">💼</span>
<span class="t-ico"><i class="fa-solid fa-briefcase"></i></span>
<span class="t-title">Businesses</span>
</a>
<a class="tile" href="/bots">
<span class="t-ico">🤖</span>
<span class="t-ico"><i class="fa-solid fa-robot"></i></span>
<span class="t-title">Bots</span>
</a>
<a class="tile" href="https://simplex.chat/file/" target="_blank" rel="noopener">
<span class="t-ico">📁</span>
<span class="t-ico"><i class="fa-solid fa-upload"></i></span>
<span class="t-title">File Upload</span>
</a>
</div>
@@ -53,15 +53,15 @@
<!-- Area: relays -->
<div class="tiles">
<a class="tile" href="/relays/chat">
<span class="t-ico">💬</span>
<span class="t-ico"><i class="fa-solid fa-comments"></i></span>
<span class="t-title">Chat Relay</span>
</a>
<a class="tile" href="/relays/file">
<span class="t-ico">📤</span>
<span class="t-ico"><i class="fa-solid fa-file-export"></i></span>
<span class="t-title">File Relay</span>
</a>
<a class="tile" href="/relays/message">
<span class="t-ico">✉️</span>
<span class="t-ico"><i class="fa-solid fa-envelope"></i></span>
<span class="t-title">Message Relay</span>
</a>
</div>
@@ -69,16 +69,16 @@
<!-- Area 3: system -->
<div class="tiles">
<a class="tile" href="/network">
<span class="t-ico">📡</span>
<span class="t-ico"><i class="fa-solid fa-tower-broadcast"></i></span>
<span class="t-title">Network</span>
</a>
<a class="tile" href="/notifications">
<span class="t-ico">🔔</span>
<span class="t-ico"><i class="fa-solid fa-bell"></i></span>
<span class="t-title">Notifications</span>
<span class="notif-badge" style="display:none;"></span>
</a>
<a class="tile" href="/settings">
<span class="t-ico">⚙️</span>
<span class="t-ico"><i class="fa-solid fa-gear"></i></span>
<span class="t-title">Settings</span>
</a>
</div>
@@ -86,7 +86,7 @@
<!-- Area 3: SimpleX (external) -->
<div class="tiles">
<a class="tile" href="https://simplex.chat/downloads/" target="_blank" rel="noopener">
<span class="t-ico">📲</span>
<span class="t-ico"><i class="fa-solid fa-download"></i></span>
<span class="t-title">Get SimpleX App</span>
</a>
</div>

View File

@@ -89,7 +89,7 @@
{% if p.address %}
<div class="addr-row" onclick="event.stopPropagation()">
<button class="btn btn-ghost copy-btn" title="Copy address"
onclick="copyAddr(event, this, '{{ p.address | e }}')">📋</button>
onclick="copyAddr(event, this, '{{ p.address | e }}')"><i class="fa-solid fa-copy"></i></button>
<a class="addr-link" href="{{ p.address }}" target="_blank" rel="noopener">{{ p.address }}</a>
</div>
{% endif %}
@@ -283,8 +283,8 @@ function onAvatarChange(input) {
function copyAddr(ev, btn, addr) {
ev.stopPropagation();
navigator.clipboard.writeText(addr).then(() => {
btn.textContent = '✓';
setTimeout(() => btn.textContent = '📋', 1500);
btn.innerHTML = '<i class="fa-solid fa-check"></i>';
setTimeout(() => btn.innerHTML = '<i class="fa-solid fa-copy"></i>', 1500);
});
}

View File

@@ -90,7 +90,7 @@
{% if profile.address %}
<div class="addr-row">
<button class="btn btn-ghost copy-btn" title="Copy address"
onclick="copyAddr(this, '{{ profile.address | e }}')">📋</button>
onclick="copyAddr(this, '{{ profile.address | e }}')"><i class="fa-solid fa-copy"></i></button>
<a class="addr-link" href="{{ profile.address }}" target="_blank" rel="noopener" id="address-text">{{ profile.address }}</a>
</div>
<div class="qr-wrap">
@@ -114,7 +114,7 @@
<p class="muted" style="margin-bottom:12px;">Auto-generated listing page for this directory bot.</p>
<div class="addr-row">
<button class="btn btn-ghost copy-btn" title="Copy URL"
onclick="copyAddr(this, location.origin + '/directory/{{ safe }}/index.html')">📋</button>
onclick="copyAddr(this, location.origin + '/directory/{{ safe }}/index.html')"><i class="fa-solid fa-copy"></i></button>
<a class="addr-link" href="/directory/{{ safe }}/index.html" target="_blank" rel="noopener">/directory/{{ safe }}/index.html</a>
</div>
</div>
@@ -148,11 +148,11 @@
<td>
<div class="flex gap-8" style="justify-content:flex-end;">
<a class="msg-btn" style="text-decoration:none;"
href="/profile/{{ profile.id }}/chat/direct/{{ c.contactId }}">💬 Chat</a>
href="/profile/{{ profile.id }}/chat/direct/{{ c.contactId }}"><i class="fa-solid fa-comments"></i> Chat</a>
<button class="msg-btn" title="Clear conversation"
onclick="clearChat('direct', {{ c.contactId }}, '{{ c.localDisplayName | e }}')">🧹 Clear</button>
onclick="clearChat('direct', {{ c.contactId }}, '{{ c.localDisplayName | e }}')"><i class="fa-solid fa-broom"></i> Clear</button>
<button class="msg-btn msg-btn-danger" title="Delete contact"
onclick="deleteContact({{ c.contactId }}, '{{ c.localDisplayName | e }}')">🗑 Delete</button>
onclick="deleteContact({{ c.contactId }}, '{{ c.localDisplayName | e }}')"><i class="fa-solid fa-trash"></i> Delete</button>
</div>
</td>
</tr>
@@ -188,7 +188,7 @@
<button class="msg-btn" onclick="joinGroup({{ gid }}, this)">Join</button>
{% else %}
<a class="msg-btn" style="text-decoration:none;"
href="/profile/{{ profile.id }}/chat/group/{{ gid }}">💬 {{ 'Broadcast' if g.is_channel else 'Chat' }}</a>
href="/profile/{{ profile.id }}/chat/group/{{ gid }}"><i class="fa-solid fa-comments"></i> {{ 'Broadcast' if g.is_channel else 'Chat' }}</a>
<button class="msg-btn" onclick="getGroupLink({{ gid }}, this)">Link</button>
<button class="msg-btn msg-btn-danger" onclick="leaveGroup({{ gid }}, '{{ name | e }}', this)">Leave</button>
{% if is_owner %}
@@ -282,7 +282,7 @@
<div class="flex-between" style="margin-bottom:16px;">
<h2 style="margin:0;">Members — <span id="members-channel-name" style="color:var(--accent);"></span></h2>
<button class="btn btn-ghost" style="padding:4px 10px;font-size:13px;"
onclick="document.getElementById('members-dialog').close()"></button>
onclick="document.getElementById('members-dialog').close()"><i class="fa-solid fa-xmark"></i></button>
</div>
<div id="members-list" style="max-height:320px;overflow-y:auto;">
<p class="muted">Loading…</p>
@@ -371,8 +371,8 @@ async function sendMsg() {
function copyAddr(btn, addr) {
navigator.clipboard.writeText(addr).then(() => {
btn.textContent = '✓';
setTimeout(() => btn.textContent = '📋', 1500);
btn.innerHTML = '<i class="fa-solid fa-check"></i>';
setTimeout(() => btn.innerHTML = '<i class="fa-solid fa-copy"></i>', 1500);
});
}

View File

@@ -8,7 +8,7 @@
</div>
<div class="card" style="text-align:center;padding:48px 24px;">
<div style="font-size:40px;line-height:1;margin-bottom:12px;">🔀</div>
<div style="font-size:40px;line-height:1;margin-bottom:12px;"><i class="fa-solid fa-shuffle"></i></div>
<strong>{{ title }} — coming soon</strong>
<p class="muted" style="margin-top:8px;">This relay isnt implemented yet.</p>
</div>

View File

@@ -100,7 +100,7 @@
</div>
<div class="theme-label">
<span>Original Light</span>
<span class="checkmark"></span>
<span class="checkmark"><i class="fa-solid fa-check"></i></span>
</div>
</div>
@@ -113,7 +113,7 @@
</div>
<div class="theme-label">
<span>Original Dark</span>
<span class="checkmark"></span>
<span class="checkmark"><i class="fa-solid fa-check"></i></span>
</div>
</div>
@@ -126,7 +126,7 @@
</div>
<div class="theme-label">
<span>Matrix</span>
<span class="checkmark"></span>
<span class="checkmark"><i class="fa-solid fa-check"></i></span>
</div>
</div>