Rich chat messages (reactions, replies, files, images); RSS poll countdown; Speakers' Corner directory page updates

- Chat: extract reactions, quoted replies, file/image data in _normalize_item
- Chat: render emoji reaction pills, reply-quote blocks, inline image previews, file blocks with Accept/Download
- Chat: reply UI (hover → set reply → preview bar above compose → send with quotedItemId)
- Chat: emoji picker strip (6 quick-react emojis) on message hover
- Chat: POST /react and POST /file/{id}/receive and GET /file/{id}/download endpoints
- Chat: file decryption via core.chat_read_file (native libsimplex FFI), served with correct MIME type
- List: RSS bot cards show live next-poll countdown (ticks every second via status API poll_next field)
- Directory: rename SimpleXXX → Speakers' Corner Online Directory throughout
- Directory: add hero banner image, About page link, QR popout, title hyperlink
- Directory: new about.html — Online Safety Act, Digital ID, 65k arrests stat, community rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jon
2026-06-07 20:23:00 +01:00
parent 432e4a5e83
commit 7c712c9ee3
8 changed files with 912 additions and 34 deletions

View File

@@ -3,8 +3,8 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SimpleXXX Directory</title>
<meta name="description" content="Find communities on the SimpleXXX network">
<title>Speakers' Corner Online Directory</title>
<meta name="description" content="Find communities on the Speakers' Corner Online network">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%230053D0'/%3E%3Cg transform='translate(50,50) rotate(45)'%3E%3Crect x='-34' y='-9' width='68' height='18' fill='%2302C0FF'/%3E%3Crect x='-9' y='-34' width='18' height='68' fill='%2302C0FF'/%3E%3Crect x='-20' y='-5' width='40' height='10' fill='%230053D0'/%3E%3Crect x='-5' y='-20' width='10' height='40' fill='%230053D0'/%3E%3C/g%3E%3C/svg%3E">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
@@ -52,6 +52,7 @@
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
@@ -321,12 +322,72 @@
.pagination button:disabled { opacity: 0.5; cursor: not-allowed; }
/* ── Hero banner ── */
.hero {
width: 100%;
max-height: 260px;
overflow: hidden;
border-radius: 16px;
margin-bottom: 28px;
position: relative;
}
.hero img {
width: 100%;
height: 260px;
object-fit: cover;
object-position: center 40%;
display: block;
}
.dir-summary {
text-align: center;
color: var(--muted);
font-size: 15px;
line-height: 1.6;
max-width: 600px;
margin: 0 auto 28px;
}
/* ── QR popout ── */
.qr-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 7px 16px; border-radius: 20px;
border: 1.5px solid var(--accent); background: transparent;
color: var(--accent); font-size: 13px; font-weight: 600;
font-family: inherit; cursor: pointer;
transition: background 0.15s;
}
.qr-btn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.qr-popout {
display: none;
position: fixed; inset: 0; z-index: 100;
background: rgba(0,0,0,0.55);
align-items: center; justify-content: center;
}
.qr-popout.open { display: flex; }
.qr-box {
background: var(--card-bg); border-radius: 20px;
padding: 28px; max-width: 320px; width: 90%;
text-align: center; box-shadow: 0 24px 60px rgba(0,0,0,0.35);
position: relative;
}
.qr-box img { width: 100%; border-radius: 10px; display: block; }
.qr-box p { margin-top: 14px; font-size: 13px; color: var(--muted); line-height: 1.5; }
.qr-close {
position: absolute; top: 12px; right: 16px;
background: none; border: none; cursor: pointer;
font-size: 20px; color: var(--muted); line-height: 1;
}
.qr-close:hover { color: var(--text); }
@media (max-width: 640px) {
#directory .entry { flex-direction: column; }
#directory .entry a.img-link { margin-right: 0; }
#directory .entry a.img-link img { width: 72px; height: 72px; min-width: 72px; min-height: 72px; border-radius: 16px; }
.search-container { flex-direction: column; align-items: stretch; }
.sort-tabs { justify-content: center; }
.hero img { height: 160px; }
}
</style>
</head>
@@ -334,12 +395,43 @@
<header>
<div class="header-inner">
<span class="logo-text">SimpleXXX Directory</span>
<a href="https://smp6.simplex.im/a#Puih5QVZOvfdnMamqJ_KBcj86dwqOJjy3sYZxKMpJnc"
target="_blank" style="text-decoration:none;">
<span class="logo-text">Speakers' Corner Online Directory</span>
</a>
<div style="display:flex;align-items:center;gap:10px;">
<a href="./about.html" style="color:var(--accent);font-size:13px;font-weight:600;text-decoration:none;">About</a>
<button class="qr-btn" onclick="document.getElementById('qr-popout').classList.add('open')">
&#9638; Scan QR
</button>
</div>
</div>
</header>
<!-- QR popout overlay -->
<div id="qr-popout" class="qr-popout" onclick="if(event.target===this)this.classList.remove('open')">
<div class="qr-box">
<button class="qr-close" onclick="document.getElementById('qr-popout').classList.remove('open')">&times;</button>
<img src="./SC-QR.png" alt="Speakers' Corner Online Directory QR code">
<p>Scan with the SimpleX app to connect to the directory, or <a href="https://smp6.simplex.im/a#Puih5QVZOvfdnMamqJ_KBcj86dwqOJjy3sYZxKMpJnc" target="_blank" style="color:var(--accent);">tap here</a>.</p>
</div>
</div>
<div class="container">
<h1>SimpleXXX Directory</h1>
<h1>
<a href="https://smp6.simplex.im/a#Puih5QVZOvfdnMamqJ_KBcj86dwqOJjy3sYZxKMpJnc"
target="_blank" style="color:inherit;text-decoration:none;">Speakers' Corner Online Directory</a>
</h1>
<div class="hero">
<img src="./thedigitalartist-flag-4628030_1920.jpg" alt="Speakers' Corner Online Directory">
</div>
<p class="dir-summary">
A community-run directory of groups and channels on the
<a href="https://simplex.chat" target="_blank" style="color:var(--accent);text-decoration:none;">SimpleX Network</a>.
Browse by activity, discover new communities, and join with one tap — no phone number or account required.
</p>
<!-- Groups / Channels tabs -->
<div class="section-tabs">