diff --git a/.gitea/workflows/lint.yml b/.gitea/workflows/lint.yml index d0a2442..7f35046 100644 --- a/.gitea/workflows/lint.yml +++ b/.gitea/workflows/lint.yml @@ -31,6 +31,18 @@ jobs: - name: Run ESLint run: npx eslint --ext .js hookshot/ + landing-fresh: + name: Landing page is rendered (matrix #11) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + # index.html's feature groups, comparison table and inlined CSS are + # generated from landing/data/*.json + landing/style.css. Fail if someone + # edited the data without re-running the renderer (or edited the output). + - name: Check landing/index.html matches its sources + run: python3 landing/build.py --check + python-lint: name: Python (ruff) runs-on: ubuntu-latest diff --git a/landing/build.py b/landing/build.py new file mode 100644 index 0000000..8e8a34c --- /dev/null +++ b/landing/build.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +"""Render the data-driven sections of landing/index.html. + +The fork feature list and the client comparison table used to be edited by +hand in two places per feature (matrix #11). They now live in landing/data/: + + features.json - the "our fork adds" groups, one short line per feature + comparison.json - the client comparison table + its "reviewed" date + +and the stylesheet lives in landing/style.css. It is inlined into index.html +rather than linked, because the LXC 139 deploy copies index.html only. + +Run `python3 landing/build.py` after editing either file and commit the +regenerated index.html (LXC 139 serves the files as-is; there is no build step +there). `--check` exits 1 if index.html is out of date, for CI. + +Only the text between `` and `` markers is +rewritten; everything else in index.html stays hand-edited. +""" +import json +import re +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +INDEX = ROOT / "index.html" +MARK = {"yes": "✓", "no": "✗", "part": "~"} + + +def render_features(groups): + out = ['
'] + for g in groups: + out.append('
') + out.append(f'

{g["title"]}

') + out.append(" ") + out.append("
") + out.append("
") + return "\n".join(out) + + +def render_cell(cell, ours): + cls = ' class="ours"' if ours else "" + if "mark" in cell: + inner = f'{MARK[cell["mark"]]}' + else: + inner = cell["text"] + if cell.get("note"): + inner += f'{cell["note"]}' + return f"{inner}" + + +def render_comparison(data): + clients = data["clients"] + cols = len(clients) + 1 + out = ["", " ", " ", " "] + for i, c in enumerate(clients): + cls = ' class="ours"' if i == 0 else "" + sub = f'{c["sub"]}' if c["sub"] else "" + out.append(f' {c["name"]}{sub}') + out += [" ", " ", " "] + for sec in data["sections"]: + out.append(f' ') + for row in sec["rows"]: + if len(row["cells"]) != len(clients): + sys.exit(f'comparison.json: "{row["feature"]}" has {len(row["cells"])} cells, expected {len(clients)}') + out.append(" ") + out.append(f' ') + out.extend(f" {render_cell(c, i == 0)}" for i, c in enumerate(row["cells"])) + out.append(" ") + out += [" ", "
{sec["title"]}
{row["feature"]}
"] + return "\n".join(out) + + +def replace_block(html, name, content): + pattern = re.compile( + rf"(?P[ \t]*).*?", re.S + ) + m = pattern.search(html) + if not m: + sys.exit(f"index.html: missing / markers") + indent = m.group("indent") + body = "\n".join(indent + line if line else line for line in content.split("\n")) + block = f"{indent}\n{body}\n{indent}" + return html[: m.start()] + block + html[m.end() :] + + +def main(): + features = json.loads((ROOT / "data" / "features.json").read_text(encoding="utf-8")) + comparison = json.loads((ROOT / "data" / "comparison.json").read_text(encoding="utf-8")) + css = (ROOT / "style.css").read_text(encoding="utf-8").rstrip("\n") + html = INDEX.read_text(encoding="utf-8") + styles = "" + new = replace_block(html, "styles", styles) + new = replace_block(new, "features", render_features(features)) + new = replace_block(new, "comparison", render_comparison(comparison)) + new = replace_block( + new, "reviewed", f'Client comparison last reviewed ' + ) + if "--check" in sys.argv: + if new != html: + sys.exit("landing/index.html is out of date: run python3 landing/build.py") + print("landing/index.html is up to date") + return + INDEX.write_text(new, encoding="utf-8") + print("wrote", INDEX) + + +if __name__ == "__main__": + main() diff --git a/landing/data/comparison.json b/landing/data/comparison.json new file mode 100644 index 0000000..58e8f54 --- /dev/null +++ b/landing/data/comparison.json @@ -0,0 +1,1869 @@ +{ + "reviewed": "2026-09-23", + "clients": [ + { + "name": "Lotus Chat", + "sub": "chat.lotusguild.org
Our Cinny fork" + }, + { + "name": "Cinny", + "sub": "cinny.in
Official" + }, + { + "name": "Element X", + "sub": "iOS & Android" + }, + { + "name": "FluffyChat", + "sub": "All platforms" + }, + { + "name": "Commet", + "sub": "Android / Win / macOS / Linux" + }, + { + "name": "Element", + "sub": "Web & Desktop" + }, + { + "name": "Nheko", + "sub": "Desktop only" + } + ], + "sections": [ + { + "title": "Platform", + "rows": [ + { + "feature": "iOS", + "cells": [ + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Android", + "cells": [ + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Desktop app", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "Cinny Desktop" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "Win, macOS & Linux" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Web browser", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Performance", + "cells": [ + { + "text": "Light" + }, + { + "text": "Light" + }, + { + "text": "Very fast", + "note": "Sliding Sync" + }, + { + "text": "Moderate" + }, + { + "text": "Moderate" + }, + { + "text": "Heavy", + "note": "Electron / JS" + }, + { + "text": "Light", + "note": "Native C++" + } + ] + } + ] + }, + { + "title": "Desktop App", + "rows": [ + { + "feature": "Native rich notifications (click→open, inline reply)", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Windows Jump List (recent rooms)", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Taskbar + volume-flyout call controls", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Focus Assist → auto Do-Not-Disturb", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Stay awake during calls", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Recursive folder drag-drop upload", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + } + ] + }, + { + "title": "Security & Encryption", + "rows": [ + { + "feature": "E2EE", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Vodozemac encryptionstronger crypto track record", + "cells": [ + { + "mark": "no", + "note": "js-sdk" + }, + { + "mark": "no", + "note": "js-sdk" + }, + { + "mark": "yes", + "note": "Rust SDK" + }, + { + "mark": "yes", + "note": "dart-sdk" + }, + { + "mark": "yes", + "note": "dart-sdk" + }, + { + "mark": "no", + "note": "js-sdk, migrating" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Device verification", + "cells": [ + { + "mark": "yes", + "note": "SAS emoji, cross-client requests show inline card" + }, + { + "mark": "part", + "note": "cross-client requests
may show unsupported" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + } + ] + }, + { + "title": "Voice & Video", + "rows": [ + { + "feature": "Voice & video calls", + "cells": [ + { + "mark": "yes", + "note": "rooms & DMs, group calls,
screenshare via Element Call" + }, + { + "mark": "part", + "note": "Element Call embed;
no ring notification" + }, + { + "mark": "yes", + "note": "MatrixRTC" + }, + { + "mark": "part", + "note": "experimental, varies
by homeserver" + }, + { + "mark": "part", + "note": "1:1 + group,
no E2EE voice rooms" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Screenshare", + "cells": [ + { + "mark": "yes", + "note": "via Element Call; fullscreen button,
independent audio mute" + }, + { + "mark": "yes", + "note": "via Element Call embed" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "XDG portals & native" + } + ] + }, + { + "feature": "Noise suppressionfilter background noise from mic", + "cells": [ + { + "mark": "yes", + "note": "3 tiers: off / browser-native /
on-device ML (RNNoise, Speex,
DTLN, DeepFilterNet 3),
works on self-hosted SFU" + }, + { + "mark": "part", + "note": "browser-native
via Element Call" + }, + { + "mark": "part", + "note": "browser-native" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "browser-native;
ML only on LiveKit Cloud" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Push-to-Talkhold key to transmit", + "cells": [ + { + "mark": "yes", + "note": "configurable hold key,
PTT badge in call bar;
M = push-to-deafen;
system-wide on Windows desktop" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "AFK auto-mutesilence mic after idle timeout", + "cells": [ + { + "mark": "yes", + "note": "1–30 min timeout,
toast notification" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Voice channel user limitadmin-set max participants", + "cells": [ + { + "mark": "yes", + "note": "per-room cap,
server-enforced
for all clients" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Call join/leave soundscue when others come & go", + "cells": [ + { + "mark": "yes", + "note": "3 styles,
Web Audio synth" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Group calls", + "cells": [ + { + "mark": "yes", + "note": "via Element Call embed" + }, + { + "mark": "yes", + "note": "via Element Call embed" + }, + { + "mark": "yes", + "note": "MatrixRTC" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "no E2EE voice rooms" + }, + { + "mark": "yes" + }, + { + "mark": "part", + "note": "partial" + } + ] + }, + { + "feature": "Incoming call notificationring + Answer/Decline", + "cells": [ + { + "mark": "yes", + "note": "DMs & group chats;
ring tone, auto-dismiss" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "native push" + }, + { + "mark": "part", + "note": "experimental" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "desktop" + }, + { + "mark": "part", + "note": "partial" + } + ] + } + ] + }, + { + "title": "Core Features", + "rows": [ + { + "feature": "Spaces", + "cells": [ + { + "mark": "yes", + "note": "best-in-class sub-space nav" + }, + { + "mark": "yes", + "note": "best-in-class sub-space nav" + }, + { + "mark": "yes", + "note": "dedicated spaces tab,
full management" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "Discord-like categories" + }, + { + "mark": "yes", + "note": "sub-spaces clunky" + }, + { + "mark": "part", + "note": "limited" + } + ] + }, + { + "feature": "Threads", + "cells": [ + { + "mark": "yes", + "note": "side panel + Slack-style per-thread notifications" + }, + { + "mark": "part", + "note": "basic, no dedicated view" + }, + { + "mark": "part", + "note": "Labs flag" + }, + { + "mark": "no", + "note": "shows as regular messages" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Polls", + "cells": [ + { + "mark": "yes", + "note": "create, vote & display;
single or multiple choice" + }, + { + "mark": "part", + "note": "display only" + }, + { + "mark": "yes", + "note": "create, vote & end" + }, + { + "mark": "yes", + "note": "added v2.3.0" + }, + { + "mark": "yes", + "note": "added v0.4.2" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Voice messagesrecord & play", + "cells": [ + { + "mark": "yes", + "note": "record & play, E2EE;
0.75×/1×/1.5×/2× speed" + }, + { + "mark": "part", + "note": "playback only" + }, + { + "mark": "yes", + "note": "record, play, variable speed,
reply with voice" + }, + { + "mark": "yes", + "note": "record & play,
pause support" + }, + { + "mark": "part", + "note": "playback confirmed;
recording unclear" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Pinned messages", + "cells": [ + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "dedicated pins view" + }, + { + "mark": "part", + "note": "view only" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Custom emoji & stickers", + "cells": [ + { + "mark": "yes", + "note": "best-in-class" + }, + { + "mark": "yes" + }, + { + "mark": "part" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "bulk upload, Signal packs" + }, + { + "mark": "part", + "note": "sometimes shows mxc:// URLs" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "GIF search / picker", + "cells": [ + { + "mark": "yes", + "note": "Giphy, Terminal TDS themed,
click-outside to close" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "privacy proxy,
uploads to homeserver" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Message search (encrypted)", + "cells": [ + { + "mark": "yes", + "note": "local cache scan; load-more
buttons extend history per room" + }, + { + "mark": "part", + "note": "server search only;
no E2EE support" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "cache scan only" + }, + { + "mark": "yes", + "note": "client-side; auto-fetches +
decrypts history during search" + }, + { + "mark": "part", + "note": "desktop only, not web" + }, + { + "mark": "part", + "note": "local DB scan" + } + ] + }, + { + "feature": "Widgets", + "cells": [ + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + } + ] + }, + { + "title": "UX & Extras", + "rows": [ + { + "feature": "Multi-account", + "cells": [ + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "supported but clunky" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no", + "note": "CLI profiles only" + } + ] + }, + { + "feature": "Image captions(text + image as one event)", + "cells": [ + { + "mark": "yes", + "note": "caption field on
image & video upload" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "scrollable captions
on media (v26.04.2)" + }, + { + "mark": "yes", + "note": "unique feature" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part" + } + ] + }, + { + "feature": "Discord-like UI", + "cells": [ + { + "mark": "yes", + "note": "best-in-class for web" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no", + "note": "WhatsApp/Telegram style" + }, + { + "mark": "yes", + "note": "most Discord-like overall" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Shared calendars & albums", + "cells": [ + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "unique to Commet" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Night Light / Blue Light Filterwarm overlay, adjustable intensity", + "cells": [ + { + "mark": "yes", + "note": "5–80% intensity slider,
Settings → Appearance" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Animated chat backgroundsCSS-only, GPU-accelerated", + "cells": [ + { + "mark": "yes", + "note": "5 animated wallpapers (rain,
stars, grid pulse, aurora,
fireflies); pause toggle" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Deleted message visibility(redacted events)", + "cells": [ + { + "mark": "yes", + "note": "\"Message deleted\" with reason
if provided" + }, + { + "mark": "no", + "note": "redacted messages hidden" + }, + { + "mark": "yes", + "note": "\"Message deleted\"
placeholder shown" + }, + { + "mark": "yes", + "note": "shows redaction notice" + }, + { + "mark": "yes", + "note": "shows redaction notice" + }, + { + "mark": "yes", + "note": "\"Message deleted\"
placeholder shown" + }, + { + "mark": "yes", + "note": "shows redaction notice" + } + ] + }, + { + "feature": "Location sharing", + "cells": [ + { + "mark": "part", + "note": "map embed view +
static share button" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "live & static,
map rendering" + }, + { + "mark": "part", + "note": "basic static" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Message forwarding", + "cells": [ + { + "mark": "yes", + "note": "forward to any room" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "via share menu" + }, + { + "mark": "yes" + }, + { + "mark": "part" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Per-message read receiptswho read each message", + "cells": [ + { + "mark": "yes", + "note": "avatar pill below message,
click for list + timestamps" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "avatar thumbnails" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "avatar row below message" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Who reactedsee who reacted with each emoji", + "cells": [ + { + "mark": "yes", + "note": "hover tooltip with names;
right-click for full avatar modal" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "tap reaction chip" + }, + { + "mark": "part" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "tooltip + click for list" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Edit history viewersee all prior versions of an edited message", + "cells": [ + { + "mark": "yes", + "note": "click \"(edited)\" label → modal
with all versions + timestamps" + }, + { + "mark": "no", + "note": "label shown, not clickable" + }, + { + "mark": "yes" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Custom status messageshown below username", + "cells": [ + { + "mark": "yes", + "note": "emoji picker, auto-clear timer
(30 min – 7 days), 64-char limit" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "via profile settings" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Online presence trackingidle/away auto-detection", + "cells": [ + { + "mark": "yes", + "note": "online on start, idle after 10 min,
unavailable when tab hidden,
offline on close; hide-status toggle" + }, + { + "mark": "part", + "note": "manual AFK only; auto idle
detection PR open, unmerged" + }, + { + "mark": "part", + "note": "basic online/offline only" + }, + { + "mark": "part", + "note": "basic online/offline only" + }, + { + "mark": "part", + "note": "inactivity monitor shipped Oct 2025;
no tab/close detection confirmed" + }, + { + "mark": "part", + "note": "3-min idle timer (shipped Nov 2023);
no tab detection, no offline on close,
no user-facing hide toggle" + }, + { + "mark": "part", + "note": "basic online/offline only" + } + ] + }, + { + "feature": "Media Gallerybrowse room images/videos/files", + "cells": [ + { + "mark": "yes", + "note": "right-side drawer;
Images/Videos/Files tabs" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Sidebar room filterfilter by name in real time", + "cells": [ + { + "mark": "yes", + "note": "Home & DMs tabs" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Favorite roomsstarred section in sidebar", + "cells": [ + { + "mark": "yes", + "note": "m.favourite tag, syncs
across devices" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "favourites section" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Invite link + QR code", + "cells": [ + { + "mark": "yes", + "note": "matrix.to URL + QR code
in invite modal & room settings" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Custom notification soundsper-category sound selector", + "cells": [ + { + "mark": "yes", + "note": "per-category sound selector; ▶ preview" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Notification quiet hourssilence notifs in a time window", + "cells": [ + { + "mark": "yes", + "note": "silence all notifs in a time window; overnight support" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Room sort ordersort sidebar rooms by activity, name, or unread", + "cells": [ + { + "mark": "yes", + "note": "Recent / A→Z / Unread First" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "sort by activity only" + }, + { + "mark": "part", + "note": "alphabetical only" + }, + { + "mark": "yes", + "note": "multiple sort options" + }, + { + "mark": "part", + "note": "limited" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Push notifications", + "cells": [ + { + "mark": "part", + "note": "web push only" + }, + { + "mark": "part", + "note": "web push only" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes" + }, + { + "mark": "yes", + "note": "desktop" + }, + { + "mark": "yes" + } + ] + }, + { + "feature": "Export room historydownload messages as file", + "cells": [ + { + "mark": "yes", + "note": "txt/json/html, date range,
E2EE-aware" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes", + "note": "has export" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Message schedulingsend later with datetime picker", + "cells": [ + { + "mark": "yes", + "note": "MSC4140, datetime picker,
cancel tray above composer" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "partial / labs" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Message bookmarks / saved messages", + "cells": [ + { + "mark": "yes", + "note": "right-click bookmark,
sidebar panel, syncs across devices" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "yes" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Collapsible long messagesauto-collapse >20 lines", + "cells": [ + { + "mark": "yes", + "note": "\"Read more\" toggle,
threshold configurable" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Inline GIF previewGiphy / Tenor links auto-embed", + "cells": [ + { + "mark": "yes", + "note": "via homeserver proxy,
no direct CDN contact" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Presence avatar border ringcolored ring shows online status", + "cells": [ + { + "mark": "yes", + "note": "green/yellow/red ring;
timeline, members, mentions" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Room emoji prefix🎮 renders larger in sidebar", + "cells": [ + { + "mark": "yes", + "note": "1.15× size; emoji picker
in all name inputs" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "Quick emoji reactions on hover1-click react, no menu needed", + "cells": [ + { + "mark": "yes", + "note": "3 recent emoji in hover bar;
dismisses emoji picker" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "part", + "note": "hover bar, no recents" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "In-app notification toastsTDS-styled, when window focused", + "cells": [ + { + "mark": "yes", + "note": "slides in bottom-right;
4 s auto-dismiss, click to jump" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + }, + { + "feature": "On-device message translationread foreign messages in your language", + "cells": [ + { + "mark": "yes", + "note": "100% on-device browser AI;
works in E2EE rooms, no cloud;
Chromium desktop & Lotus app" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + }, + { + "mark": "no" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/landing/data/features.json b/landing/data/features.json new file mode 100644 index 0000000..d369216 --- /dev/null +++ b/landing/data/features.json @@ -0,0 +1,72 @@ +[ + { + "title": "Calls & Voice", + "items": [ + "Voice rooms with our own Element Call fork on a self-hosted LiveKit SFU", + "Push-to-talk and push-to-deafen, with system-wide hotkeys on the Windows app (work while a game has focus)", + "On-device noise suppression: Off, browser-native, or ML (RNNoise, Speex, DTLN, DeepFilterNet 3)", + "In-call soundboard with your own clips, synced across your devices", + "Incoming-call ring with Answer/Decline, join/leave sounds, draggable picture-in-picture window", + "Screenshare fullscreen and audio mute; per-user mic/screenshare bitrate and framerate caps", + "Server-enforced room limits and permissions (max participants, audio-only, no screenshare) for every Matrix client", + "AFK auto-mute after a configurable idle time" + ] + }, + { + "title": "Messaging", + "items": [ + "Threads with a side panel, unread chips and per-thread notification overrides", + "Voice messages with 0.75×–2× playback (MSC3245, E2EE)", + "Polls, message scheduling (MSC4140), forwarding, captions and location sharing", + "Pinned messages, saved messages/bookmarks and private notes on people, synced across devices", + "Who-reacted viewer, read receipt avatars, quick reactions on hover", + "GIF picker (Giphy), custom emoji & sticker packs, math/LaTeX (KaTeX)", + "On-device message translation: text never leaves your machine, works in encrypted rooms" + ] + }, + { + "title": "Media & Links", + "items": [ + "Click-to-play inline embeds: YouTube, Vimeo, Twitch, TikTok, X, Instagram, Reddit, Bluesky, Spotify, SoundCloud, Apple Music, Tidal and more", + "Rich link cards for GitHub, Steam, Wikipedia, IMDb, npm and others; animated GIF previews", + "Media gallery for every image, video and file shared in a room", + "Optional image compression, folder drag-and-drop upload" + ] + }, + { + "title": "Privacy & Security", + "items": [ + "Private read receipts, hide typing, hide online status", + "Tracking-parameter stripping (utm_*, fbclid, YouTube si= and ~40 more), on your device", + "Opt-in on-device search index for encrypted rooms, wiped on logout", + "Device verification fixes (cross-client emoji SAS) and a per-member session panel" + ] + }, + { + "title": "Look & Feel", + "items": [ + "Lotus Terminal theme, night-light filter, glassmorphism sidebar", + "Animated chat backgrounds and 11 seasonal overlays (all respect reduced motion)", + "99 animated avatar decorations, visible to other Lotus users (MSC4133)", + "Custom status with emoji and auto-clear, Discord-style presence rings", + "Settings sync across devices (theme, layout, notification and privacy preferences)" + ] + }, + { + "title": "Desktop App", + "items": [ + "Windows, macOS and Linux (Tauri)", + "Rich Windows notifications with inline reply; Focus Assist sync", + "Taskbar call controls, jump list and upload progress; tray with call status", + "Stays awake during calls, reconnects on network changes, updates itself" + ] + }, + { + "title": "Rooms & Moderation", + "items": [ + "Knock-to-join with an admin approve/deny panel and live pending-count badge", + "Invite links with QR codes, favourites, room filter, room history export (txt/json/html)", + "Room activity & mod log, stats panel, server ACL editor, policy-list viewer" + ] + } +] diff --git a/landing/index.html b/landing/index.html index 47e02e5..fa2d411 100644 --- a/landing/index.html +++ b/landing/index.html @@ -4,6 +4,7 @@ Lotus Guild Matrix + +
@@ -553,11 +578,83 @@
-

- Our Lotus Guild fork of Cinny adds: voice message recording & playback with 0.75×/1×/1.5×/2× speed control (MSC3245, E2EE), device verification fix (cross-client SAS emoji + inline cards), per-member device session panel with per-device verify buttons, full Discord-style presence tracking (online on startup, idle/away after 10 min inactivity, unavailable when tab hidden, offline on close — with a “Hide Online Status” privacy toggle), presence status indicators (online/busy/away dots) in member lists, incoming call ring + Answer/Decline (DMs & group chats), GIF picker (Giphy), emoji & sticker picker (custom packs, stickers send as m.sticker events), pinned messages panel (pin icon in room header, pin/unpin from message menu), who-reacted viewer (hover any reaction for a name tooltip; right-click for a full avatar list), draggable+resizable picture-in-picture call window, poll creation & voting (single or multiple choice, 2–10 options), message forwarding, image/video captions, location sharing (map view + send), deleted message placeholders, per-message read receipt avatars (click for full list with timestamps), private read receipts toggle (Settings → Privacy), screenshare fullscreen button, screenshare audio mute (mute a screenshare’s audio without leaving the call), PTT (Push-to-Talk with configurable hold key), push-to-deafen (M key, configurable in Settings → Calls), custom status messages with emoji picker + auto-clear timer (30 min – 7 days) shown below usernames, encrypted room search via local cache scan with per-room “Load more” history buttons, a dedicated Privacy settings section (hide typing, hide online status), sidebar room filter (search rooms by name in Home and DMs tabs), favorite rooms (star any room, syncs across devices via m.favourite tag), media gallery drawer (browse all images/videos/files shared in a room), invite link + QR code (in both invite modal and room settings), knock-to-join support (Request to Join button + admin Approve/Deny panel), code syntax highlighting in Lotus Terminal mode (keywords, strings, numbers, comments, functions), night light / blue light filter (warm orange overlay with adjustable intensity in Settings → Appearance), message length counter in the composer, and the Lotus Terminal design theme (with TDS-styled orange typing indicator dots). - Also added in June 2026: message scheduling (MSC4140, datetime picker, cancel tray), saved messages / bookmarks (right-click any message, sidebar panel, syncs across devices), room history export (txt/json/html, date range, E2EE-aware), room activity & mod log (joins, kicks, bans, power level changes), server ACL editor (allow/deny lists, wildcard validation, power-level gated), room stats panel (top members, top reactions, media breakdown, 24h activity heatmap), opt-in image compression (Canvas API at 0.82 quality, shows before/after sizes), 13 domain-specific URL preview cards (YouTube, Vimeo, Twitch, Reddit, X/Twitter, Spotify, Steam, IMDb, Wikipedia, GitHub, Discord, npm, Stack Overflow), inline GIF preview (Giphy & Tenor share links auto-embed as animated GIFs via the homeserver proxy), policy list viewer (admin panel for m.policy.rule.* ban list rooms — complements Draupnir), collapsible long messages (auto-collapse > 20 lines with “Read more” toggle, threshold configurable), message send animation (0.15 s fade+scale on own messages, respects prefers-reduced-motion), right-click room context menu improvements (Mute with duration submenu 15 min–indefinite, Copy Room Link, Mark as Read, Leave Room, Room Settings), quick emoji reactions directly on message hover (3 most-recent emoji in the hover bar, single click to react), in-app notification toasts (TDS-styled slide-in card from bottom-right when the window is focused, 4 s auto-dismiss, click to navigate), presence avatar border ring (2px green/yellow/red ring on user avatars in the timeline, members list, @mention autocomplete, and notifications), room emoji prefix support (leading emoji renders at 1.15× in the sidebar; emoji picker button on all room name inputs), glassmorphism sidebar toggle (Settings → Appearance, off by default; frosted blur effect lets chat backgrounds show through the sidebar — fixed in June 2026 to mirror the background onto document.body so the blur has content to work through), and 5 CSS-only animated chat backgrounds: Digital Rain (two-layer vertical stripe scroll with parallax), Star Drift (three-layer radial-gradient dots drifting diagonally), Grid Pulse (neon grid lines expanding/contracting), Aurora Flow (sweeping radial gradient ellipses on a 200% canvas), and Fireflies (three layers of glowing dots drifting). All respect prefers-reduced-motion and include a "Pause Background Animations" toggle in Settings → Appearance. Also added: AFK auto-mute for voice calls (mic silenced after a configurable idle timeout of 1–30 min detected via Web Audio AnalyserNode; in-app toast confirms the action; toggle + duration selector in Settings → Calls), knock-to-join admin badge (a live warning badge on the Members button in the room header counts pending knock requests in real time, visible only to users with sufficient invite permissions), voice channel user limit (admins set a max-participant cap per room via the io.lotus.voice_limit state event; enforced server-side for every Matrix client by a guard that fronts the LiveKit JWT issuer and refuses tokens once a room is full, with a “Channel Full (N/N)” message and disabled Join button in Lotus Chat, while members already in the call can always rejoin), and custom call join/leave sound effects (a local cue plays when someone enters or leaves a call you’re in — tracked via MatrixRTC membership changes, synthesized in-browser with the Web Audio API so no assets are bundled; choose Chime, Soft, Retro, or off in Settings → Calls). Also shipped in June 2026: 11 seasonal & holiday theme overlays (Halloween, Christmas, New Year, Autumn, Valentine’s Day, St. Patrick’s Day, Earth Day, Lunar New Year, April Fools’, Deep Space, and Retro Arcade — auto-selected by date with a manual override in Settings → Appearance; Retro Arcade and Deep Space were re-tuned in September 2026 so the neon grid, horizon and HUD frame the timeline instead of overlapping message text; CSS-only particle systems, fully animated, all respect prefers-reduced-motion), improved chat background animations (phosphor-flicker brightness layer on Digital Rain, seamless per-tile star looping, per-layer independent aurora positions for a true fluid sweep, organic firefly bioluminescence via independent brightness+opacity pulses), user-to-user private notes (freeform text note on any user’s profile, visible only to you, auto-saves after 800 ms, syncs across devices via io.lotus.user_notes account data), presence status revert fix (status message now re-read from storage on every activity event so changing your custom status in Profile Settings is never silently overwritten), and voice ringing fix for public space channels (joining a persistent voice room no longer triggers the incoming-call ring; the m.space.parent state key lookup was corrected to use getStateEvents instead of the single-key variant that always returned undefined), and avatar decorations (99 curated APNG overlay frames — Gaming, Cyber, Space, Fantasy, Elements, Japanese, Nature, Spooky, Cozy — that animate around your avatar in the message timeline, members list, and @mention autocomplete; decoration is stored in your public Matrix profile via MSC4133 so other Lotus Chat users see it on your avatar in real time; select from Settings → Account → Avatar Decoration; includes a Lotus Flower decoration), and a 3-tier call noise-suppression control (Settings → General → Calls — Off, Browser-native, or on-device ML) where the ML tier runs a choice of on-device models (RNNoise, Speex, DTLN, or DeepFilterNet 3) entirely in your browser to strip keyboard clatter, fans, and background chatter from your mic before it reaches the call; unlike Krisp-style cloud filters it needs no LiveKit Cloud subscription and works on our self-hosted SFU. Powered by our own self-built fork of Element Call, so call behavior is tuned for Lotus Guild rather than locked to an upstream bundle. Most recently: an in-call soundboard (upload your own short audio clips — like custom emojis, synced across your devices — and play them into a call so everyone hears them, mixed in as a real call track), per-user call quality controls (cap your microphone/screenshare bitrate and screenshare framerate for slower connections, in Settings → Calls), and server-enforced room call permissions (admins can turn off screen sharing or make a room audio-only, enforced for every Matrix client by the same LiveKit token guard — and it stops an in-progress screenshare within seconds of being switched off). Also new — a native desktop app (Windows/macOS/Linux, built on Tauri): rich Windows toast notifications you can click to open the room or reply to inline, Windows Focus Assist sync (auto-silences Lotus while Windows is in Do-Not-Disturb), a taskbar Jump List of your most-active rooms, taskbar thumbnail call controls (mute/deafen/end) plus call status in the volume flyout, the system stays awake during calls, prompt reconnect on network changes, an opt-in Lotus-styled window title bar, recursive folder drag-drop (drop a whole folder to upload everything inside), a drag-to-reorder composer toolbar, a draft-saved indicator, and automatic background updates with a one-click update toast. Newest (July 2026): full thread support (a dedicated side panel with its own composer, "N replies" chips with unread badges on root messages, per-thread read tracking, and Slack-style thread notifications — by default you're only pinged for threads you're in or where you're mentioned, with per-thread All/Mentions/Mute overrides that sync across devices), math/LaTeX rendering (KaTeX — $inline$ and $$block$$, lazy-loaded), an opt-in persistent search index for encrypted rooms (survives reloads; stored only on your device, clearable, wiped on logout), hardened atomic session storage with cross-tab logout sync, a built-in crypto-diagnostics capture tool for debugging E2EE issues, and inline media embeds (YouTube/Shorts, Vimeo, Dailymotion, Streamable, Twitch, Loom & Kick play in place as video; TikTok, X/Twitter, Instagram, Reddit & Bluesky posts render inline; Spotify, SoundCloud, Apple Music & Tidal get a built-in audio player — all behind a privacy-friendly click-to-play facade so nothing loads from the third party until you press play, with a toggle in Settings → General), and on-device message translation (a “Translate” button on any foreign-language message renders it inline in your own language, with a “Translated from <language> · Show original” toggle and an optional auto-translate mode; set your language in Settings → General, default English). Translation runs 100% on your device using the browser’s built-in on-device translation AI — message text never leaves your machine and never touches Google, DeepL, Microsoft or any cloud translation service, so it works even in end-to-end encrypted rooms without weakening encryption. Available on Chromium desktop browsers (Chrome/Edge) and the Lotus desktop app; gracefully hidden where the browser can’t run it (Firefox/Safari/mobile). Newest (September 2026): settings sync across devices (your theme, composer layout, notification and privacy preferences follow you to every Lotus login via io.lotus.settings account data; device-bound settings like zoom, volumes and noise suppression stay local), tracking-parameter stripping (links you paste, send or even just see lose utm_*, fbclid, YouTube si=, Amazon affiliate tags and ~40 more — entirely on your device, toggle in Settings → Privacy), system-wide voice hotkeys on the Windows desktop app (push-to-talk and deafen keep working while a fullscreen game has focus, without stealing the key from the game), a PWA install prompt (Chromium one-tap install, iOS “Add to Home Screen” hint) with a mention count badge on the app icon, and a tidied message composer on phones (one row, no wrapping, larger touch targets). - Prefer the unmodified upstream? cinny.in works with our homeserver — set it to matrix.lotusguild.org. -

+

Our Lotus Guild fork of Cinny adds:

+ +
+
+

Calls & Voice

+
    +
  • Voice rooms with our own Element Call fork on a self-hosted LiveKit SFU
  • +
  • Push-to-talk and push-to-deafen, with system-wide hotkeys on the Windows app (work while a game has focus)
  • +
  • On-device noise suppression: Off, browser-native, or ML (RNNoise, Speex, DTLN, DeepFilterNet 3)
  • +
  • In-call soundboard with your own clips, synced across your devices
  • +
  • Incoming-call ring with Answer/Decline, join/leave sounds, draggable picture-in-picture window
  • +
  • Screenshare fullscreen and audio mute; per-user mic/screenshare bitrate and framerate caps
  • +
  • Server-enforced room limits and permissions (max participants, audio-only, no screenshare) for every Matrix client
  • +
  • AFK auto-mute after a configurable idle time
  • +
+
+
+

Messaging

+
    +
  • Threads with a side panel, unread chips and per-thread notification overrides
  • +
  • Voice messages with 0.75×–2× playback (MSC3245, E2EE)
  • +
  • Polls, message scheduling (MSC4140), forwarding, captions and location sharing
  • +
  • Pinned messages, saved messages/bookmarks and private notes on people, synced across devices
  • +
  • Who-reacted viewer, read receipt avatars, quick reactions on hover
  • +
  • GIF picker (Giphy), custom emoji & sticker packs, math/LaTeX (KaTeX)
  • +
  • On-device message translation: text never leaves your machine, works in encrypted rooms
  • +
+
+
+

Media & Links

+
    +
  • Click-to-play inline embeds: YouTube, Vimeo, Twitch, TikTok, X, Instagram, Reddit, Bluesky, Spotify, SoundCloud, Apple Music, Tidal and more
  • +
  • Rich link cards for GitHub, Steam, Wikipedia, IMDb, npm and others; animated GIF previews
  • +
  • Media gallery for every image, video and file shared in a room
  • +
  • Optional image compression, folder drag-and-drop upload
  • +
+
+
+

Privacy & Security

+
    +
  • Private read receipts, hide typing, hide online status
  • +
  • Tracking-parameter stripping (utm_*, fbclid, YouTube si= and ~40 more), on your device
  • +
  • Opt-in on-device search index for encrypted rooms, wiped on logout
  • +
  • Device verification fixes (cross-client emoji SAS) and a per-member session panel
  • +
+
+
+

Look & Feel

+
    +
  • Lotus Terminal theme, night-light filter, glassmorphism sidebar
  • +
  • Animated chat backgrounds and 11 seasonal overlays (all respect reduced motion)
  • +
  • 99 animated avatar decorations, visible to other Lotus users (MSC4133)
  • +
  • Custom status with emoji and auto-clear, Discord-style presence rings
  • +
  • Settings sync across devices (theme, layout, notification and privacy preferences)
  • +
+
+
+

Desktop App

+
    +
  • Windows, macOS and Linux (Tauri)
  • +
  • Rich Windows notifications with inline reply; Focus Assist sync
  • +
  • Taskbar call controls, jump list and upload progress; tray with call status
  • +
  • Stays awake during calls, reconnects on network changes, updates itself
  • +
+
+
+

Rooms & Moderation

+
    +
  • Knock-to-join with an admin approve/deny panel and live pending-count badge
  • +
  • Invite links with QR codes, favourites, room filter, room history export (txt/json/html)
  • +
  • Room activity & mod log, stats panel, server ACL editor, policy-list viewer
  • +
+
+
+ +

Full list in LOTUS_FEATURES.md.

+

Prefer the unmodified upstream? cinny.in works with our homeserver — set it to matrix.lotusguild.org.

Already signed in? Jump straight into the community:

@@ -676,9 +773,13 @@
-

Client Feature Comparison — June 2026 (latest)

+

Client Feature Comparison

+

+Client comparison last reviewed +

← swipe to compare →

+ @@ -693,8 +794,6 @@ - - @@ -746,9 +845,6 @@ - - - @@ -810,7 +906,6 @@ - @@ -842,8 +937,6 @@ - - @@ -935,8 +1028,6 @@ - - @@ -1028,8 +1119,6 @@ - - @@ -1351,9 +1440,9 @@ -
Platform
iOSHeavyElectron / JS LightNative C++
Desktop App
Native rich notifications (click→open, inline reply)✗ ✗
Security & Encryption
E2EE✓ ✓
Voice & Video
Voice & video calls✓desktop ~partial
Core Features
Spaces✓ ✗
UX & Extras
Multi-account✗ ✗
+
diff --git a/landing/style.css b/landing/style.css new file mode 100644 index 0000000..06417ab --- /dev/null +++ b/landing/style.css @@ -0,0 +1,520 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } + +body { + background: #0a0a0a; + color: #e0e0e0; + font-family: 'Segoe UI', system-ui, -apple-system, sans-serif; + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 20px 16px 40px; +} + +body::before { + content: ''; + position: fixed; + top: 50%; + left: 50%; + width: 900px; + height: 900px; + transform: translate(-50%, -50%); + background: radial-gradient(circle, rgba(152, 0, 0, 0.07) 0%, transparent 65%); + pointer-events: none; + z-index: 0; +} + +.container { + position: relative; + z-index: 1; + text-align: center; + width: 100%; + max-width: 900px; +} + +.logo { + width: 140px; + height: 140px; + margin: 0 auto 28px; + border-radius: 50%; + filter: drop-shadow(0 0 24px rgba(152, 0, 0, 0.35)); + animation: float 6s ease-in-out infinite; +} + +@keyframes float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-8px); } +} + +h1 { + font-size: 2rem; + font-weight: 300; + letter-spacing: 0.15em; + text-transform: uppercase; + color: #fff; + margin-bottom: 4px; +} + +h1 span { color: #980000; font-weight: 600; } + +.subtitle { + font-size: 0.85rem; + color: #555; + letter-spacing: 0.3em; + text-transform: uppercase; + margin-bottom: 36px; +} + +/* ─── Cards / Panels ─── */ +.card { + background: linear-gradient(145deg, rgba(22,22,22,0.95), rgba(14,14,14,0.98)); + border: 1px solid rgba(152,0,0,0.2); + border-radius: 16px; + padding: 32px; + max-width: 560px; + margin: 0 auto; +} + +.card h2 { + font-size: 0.9rem; + font-weight: 500; + color: #980000; + text-transform: uppercase; + letter-spacing: 0.15em; + margin-bottom: 20px; +} + +/* ─── Steps ─── */ +.steps { list-style: none; text-align: left; margin-bottom: 28px; } + +.steps li { + display: flex; + align-items: flex-start; + gap: 14px; + padding: 12px 0; + border-bottom: 1px solid rgba(255,255,255,0.04); +} + +.steps li:last-child { border-bottom: none; } + +.step-num { + flex-shrink: 0; + width: 28px; height: 28px; + background: rgba(152,0,0,0.12); + border: 1px solid rgba(152,0,0,0.35); + border-radius: 50%; + display: flex; align-items: center; justify-content: center; + font-size: 0.8rem; font-weight: 600; color: #c44; +} + +.step-text { padding-top: 3px; font-size: 0.95rem; line-height: 1.5; color: #bbb; } +.step-text strong { color: #e0e0e0; } + +.homeserver { + display: inline-block; + background: rgba(152,0,0,0.1); + border: 1px solid rgba(152,0,0,0.25); + color: #e88; + font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; + font-size: 0.85rem; + padding: 3px 10px; + border-radius: 6px; +} + +a { color: #c44; text-decoration: none; } +.step-text a, .option-block a { + border-bottom: 1px solid rgba(204,68,68,0.3); + transition: border-color 0.2s; +} +.step-text a:hover, .option-block a:hover { border-bottom-color: #c44; } + +/* ─── Or divider ─── */ +.or-divider { + display: flex; align-items: center; gap: 10px; + margin: 4px 0 10px 42px; + color: #444; font-size: 0.78rem; letter-spacing: 0.1em; text-transform: uppercase; +} +.or-divider::before, .or-divider::after { + content: ''; flex: 1; height: 1px; background: rgba(255,255,255,0.06); +} + +.option-block { + margin-left: 42px; + padding: 12px 14px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 8px; + text-align: left; font-size: 0.88rem; color: #888; line-height: 1.5; +} + +.divider { height: 1px; background: rgba(152,0,0,0.15); margin: 24px 0; } + +/* ─── Tags ─── */ +.tag { + font-size: 0.65rem; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.1); + padding: 2px 8px; border-radius: 4px; + text-transform: uppercase; letter-spacing: 0.05em; color: #bbb; + white-space: nowrap; +} +.tag.voice { background: rgba(0,180,120,0.2); border-color: rgba(0,180,120,0.4); color: #5effc4; } +.tag.beta { background: rgba(255,180,0,0.15); border-color: rgba(255,180,0,0.3); color: #ffcc55; } +.tag.dev { background: rgba(160,80,255,0.15); border-color: rgba(160,80,255,0.3); color: #cc88ff; } +.tag.rust { background: rgba(80,140,255,0.15); border-color: rgba(80,140,255,0.3); color: #88aaff; } +.tag.warn { background: rgba(255,140,0,0.15); border-color: rgba(255,140,0,0.3); color: #ffaa44; } +.tag.dim { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.08); color: #666; } + +/* ─── Featured client ─── */ +.client-featured { margin-bottom: 16px; } + +.client-featured a { + display: flex; flex-direction: column; align-items: center; gap: 6px; + background: linear-gradient(135deg, rgba(152,0,0,0.25), rgba(120,0,0,0.15)); + border: 1px solid rgba(152,0,0,0.55); + color: #fff; text-decoration: none; + padding: 18px 24px; border-radius: 12px; + transition: all 0.25s ease; + box-shadow: 0 0 20px rgba(152,0,0,0.1); +} +.client-featured a:hover { + background: linear-gradient(135deg, rgba(152,0,0,0.38), rgba(120,0,0,0.25)); + border-color: rgba(152,0,0,0.8); + box-shadow: 0 0 32px rgba(152,0,0,0.25); + transform: translateY(-2px); +} + +.client-name { font-size: 1.15rem; font-weight: 600; letter-spacing: 0.05em; } +.client-desc { font-size: 0.82rem; color: #ccc; } + +.tag-row { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; margin-top: 2px; } + +/* Also-available note */ +.also-available { + font-size: 0.78rem; color: #555; margin-top: 8px; line-height: 1.6; +} +.also-available a { color: #666; border-bottom: 1px solid rgba(102,102,102,0.3); transition: color 0.2s; } +.also-available a:hover { color: #999; } + +/* ─── Space join ─── */ +.space-join { + margin-top: 20px; padding: 16px 20px; + background: rgba(152,0,0,0.06); + border: 1px solid rgba(152,0,0,0.2); border-radius: 10px; +} +.space-join p { font-size: 0.82rem; color: #666; margin-bottom: 10px; } +.space-join a { + display: inline-block; + background: rgba(152,0,0,0.15); + border: 1px solid rgba(152,0,0,0.35); + color: #c44; text-decoration: none; + padding: 8px 20px; border-radius: 8px; font-size: 0.88rem; + transition: all 0.25s ease; +} +.space-join a:hover { background: rgba(152,0,0,0.25); color: #fff; } + +/* ─── Secondary client cards ─── */ +.clients-section h3 { + font-size: 0.8rem; font-weight: 500; color: #666; + text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 14px; +} + +.client-group { margin-bottom: 18px; } + +.client-group-label { + font-size: 0.75rem; color: #555; + text-transform: uppercase; letter-spacing: 0.1em; + margin-bottom: 8px; +} + +.client-cards { display: flex; flex-direction: column; gap: 8px; } + +.client-card { + display: flex; flex-direction: column; gap: 6px; + background: rgba(255,255,255,0.025); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 10px; padding: 12px 14px; text-align: left; + transition: border-color 0.2s; +} +.client-card:hover { border-color: rgba(152,0,0,0.3); } + +.client-card-top { + display: flex; align-items: center; + justify-content: space-between; gap: 10px; flex-wrap: wrap; +} + +.client-card-name { + font-size: 0.95rem; font-weight: 600; color: #e0e0e0; + text-decoration: none; + border-bottom: 1px solid rgba(204,68,68,0.2); + transition: color 0.2s, border-color 0.2s; +} +.client-card-name:hover { color: #c44; border-bottom-color: #c44; } + +.client-card-tags { display: flex; gap: 5px; flex-wrap: wrap; } +.client-card-desc { font-size: 0.8rem; color: #666; line-height: 1.45; } + +/* ─── Comparison table ─── */ +.comparison-section { + margin-top: 32px; + background: linear-gradient(145deg, rgba(22,22,22,0.95), rgba(14,14,14,0.98)); + border: 1px solid rgba(152,0,0,0.2); + border-radius: 16px; + overflow: hidden; +} + +.comparison-title { + font-size: 0.78rem; font-weight: 500; color: #980000; + text-transform: uppercase; letter-spacing: 0.15em; + padding: 16px 20px 12px; + border-bottom: 1px solid rgba(152,0,0,0.1); +} + +.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; } + +table { + width: 100%; border-collapse: collapse; + font-size: 0.78rem; min-width: 760px; +} + +thead tr { border-bottom: 1px solid rgba(152,0,0,0.15); } + +th { + padding: 10px 8px; text-align: center; + font-size: 0.7rem; font-weight: 600; + color: #888; text-transform: uppercase; letter-spacing: 0.08em; + background: rgba(255,255,255,0.02); +} +th:first-child { text-align: left; padding-left: 16px; min-width: 140px; } +th.ours { color: #c66; } + +th small { display: block; font-size: 0.6rem; font-weight: 400; color: #555; margin-top: 2px; text-transform: none; letter-spacing: 0; } + +tr { border-bottom: 1px solid rgba(255,255,255,0.03); } +tr:last-child { border-bottom: none; } + +tr.section-header td { + background: rgba(152,0,0,0.06); + color: #770000; font-weight: 600; + font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.12em; + padding: 6px 8px 5px 16px; text-align: left; +} + +td { + padding: 8px 8px; text-align: center; vertical-align: middle; + color: #888; line-height: 1.3; +} +td:first-child { + text-align: left; padding-left: 16px; + color: #aaa; font-size: 0.78rem; +} +td small { display: block; font-size: 0.68rem; color: #555; margin-top: 1px; } + +.yes { color: #5effc4; font-size: 1rem; } +.part { color: #ffcc55; font-size: 0.9rem; } +.no { color: #444; font-size: 1rem; } + +/* highlight our hosted client column */ +th.ours, td.ours { background: rgba(152,0,0,0.04); } + +/* ─── Legend ─── */ +.legend { + display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; + padding: 10px 16px 14px; + border-top: 1px solid rgba(255,255,255,0.04); + font-size: 0.72rem; color: #555; +} +.legend span { display: flex; align-items: center; gap: 5px; } + +/* ─── Security note ─── */ +.security-note { + margin: 0 20px 16px; + padding: 10px 14px; + background: rgba(80,140,255,0.04); + border: 1px solid rgba(80,140,255,0.12); + border-radius: 8px; + font-size: 0.76rem; color: #556; line-height: 1.55; text-align: left; +} +.security-note strong { color: #88aaff; } + +.all-clients { margin-top: 14px; } +.all-clients a { + font-size: 0.78rem; color: #555; + border-bottom: 1px solid rgba(85,85,85,0.3); + transition: color 0.2s, border-color 0.2s; +} +.all-clients a:hover { color: #888; border-bottom-color: #888; } + +/* ─── Server info ─── */ +.server-info { + margin-top: 24px; + background: linear-gradient(145deg, rgba(22,22,22,0.95), rgba(14,14,14,0.98)); + border: 1px solid rgba(152,0,0,0.2); + border-radius: 12px; overflow: hidden; +} +.server-info-title { + font-size: 0.78rem; font-weight: 500; color: #980000; + text-transform: uppercase; letter-spacing: 0.15em; + padding: 14px 20px 10px; + border-bottom: 1px solid rgba(152,0,0,0.1); +} +.info-grid { display: grid; grid-template-columns: 1fr 1fr; } +.info-item { + padding: 12px 18px; + border-bottom: 1px solid rgba(255,255,255,0.03); + border-right: 1px solid rgba(255,255,255,0.03); + text-align: left; +} +.info-item:nth-child(even) { border-right: none; } +.info-item:nth-last-child(-n+2) { border-bottom: none; } +.info-label { font-size: 0.7rem; color: #555; text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 3px; } +.info-value { font-size: 0.88rem; color: #ccc; } + +.privacy-strip { + padding: 10px 18px; + border-top: 1px solid rgba(152,0,0,0.1); + display: flex; gap: 18px; justify-content: center; flex-wrap: wrap; +} +.privacy-badge { font-size: 0.75rem; color: #5effc4; display: flex; align-items: center; gap: 5px; } +.privacy-badge::before { content: '✓'; font-weight: 700; } + +/* ─── Legal / contact / footer ─── */ +.legal-note { + margin-top: 24px; padding: 12px 18px; + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.05); + border-radius: 8px; font-size: 0.75rem; color: #444; + line-height: 1.6; text-align: left; +} +.legal-note a { color: #555; border-bottom: 1px solid rgba(85,85,85,0.3); } +.legal-note a:hover { color: #888; } + +.contact { + margin-top: 24px; padding: 16px; + background: linear-gradient(145deg, rgba(22,22,22,0.95), rgba(14,14,14,0.98)); + border: 1px solid rgba(152,0,0,0.2); + border-radius: 12px; text-align: center; +} + +.footer { + margin-top: 20px; font-size: 0.72rem; color: #383838; + letter-spacing: 0.04em; + display: flex; justify-content: center; gap: 14px; flex-wrap: wrap; +} +.footer a { color: #444; transition: color 0.2s; } +.footer a:hover { color: #777; } + +/* ─── Sticky first table column (all screen sizes) ─── */ +td:first-child { + position: sticky; + left: 0; + z-index: 1; + background: #0d0d0d; +} +th:first-child { + position: sticky; + left: 0; + z-index: 2; + background: #111; +} +tr.section-header td { + /* section headers span full width — override sticky bg */ + position: static; + background: rgba(152,0,0,0.06); +} + +/* Scroll hint — visible only on mobile via JS class */ +.scroll-hint { + display: none; + font-size: 0.72rem; + color: #3a3a3a; + padding: 0 16px 10px; + letter-spacing: 0.05em; +} + +/* ─── Tablet (≤ 700px) ─── */ +@media (max-width: 700px) { + body { align-items: flex-start; } +} + +/* ─── Mobile (≤ 540px) ─── */ +@media (max-width: 540px) { + body { padding: 20px 12px 40px; } + + .logo { width: 100px; height: 100px; margin-bottom: 20px; } + h1 { font-size: 1.5rem; letter-spacing: 0.1em; } + .subtitle { font-size: 0.78rem; letter-spacing: 0.18em; margin-bottom: 26px; } + + .card { padding: 22px 16px; } + .step-text { font-size: 0.88rem; } + .homeserver { font-size: 0.75rem; word-break: break-all; } + .or-divider, .option-block { margin-left: 0; } + + .client-card-top { flex-direction: column; align-items: flex-start; gap: 6px; } + .client-card-desc { font-size: 0.79rem; } + .client-card-tags { gap: 4px; } + + .also-available { font-size: 0.75rem; } + + /* Table */ + .scroll-hint { display: block; } + table { font-size: 0.71rem; min-width: 620px; } + th { padding: 8px 5px; font-size: 0.6rem; } + th:first-child { min-width: 100px; padding-left: 10px; } + td { padding: 7px 5px; } + td:first-child { font-size: 0.71rem; padding-left: 10px; } + td small, th small { font-size: 0.58rem; } + .yes { font-size: 0.88rem; } + .part { font-size: 0.82rem; } + .no { font-size: 0.88rem; } + .comparison-title { font-size: 0.7rem; padding: 12px 14px 8px; letter-spacing: 0.1em; } + .security-note { margin: 10px 12px 4px; font-size: 0.71rem; } + .legend { padding: 8px 12px 12px; gap: 12px; font-size: 0.68rem; } + + /* Server info */ + .server-info-title { font-size: 0.7rem; padding: 12px 14px 8px; } + .info-item { padding: 10px 14px; } + .info-label { font-size: 0.65rem; } + .info-value { font-size: 0.82rem; } + .privacy-strip { flex-direction: column; align-items: center; gap: 8px; padding: 10px 14px; } + .privacy-badge { font-size: 0.71rem; } + + /* Legal / footer */ + .legal-note { font-size: 0.72rem; padding: 11px 14px; } + .contact { padding: 14px; } + .footer { gap: 8px; font-size: 0.68rem; } +} + +/* ─── Very small (≤ 380px) ─── */ +@media (max-width: 380px) { + h1 { font-size: 1.3rem; } + .logo { width: 84px; height: 84px; } + .card { padding: 18px 12px; } + table { font-size: 0.66rem; min-width: 580px; } + th { font-size: 0.55rem; padding: 7px 4px; } + td { padding: 6px 4px; } + td:first-child { font-size: 0.66rem; min-width: 90px; } +} + +/* ── Fork feature groups (matrix #11) ── */ +.fork-intro { font-size: 0.82rem; color: #888; margin: 14px 0 8px; line-height: 1.5; } +.fork-features { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 10px; + margin: 0 0 12px; +} +.fork-group { + background: rgba(255,255,255,0.02); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 8px; + padding: 10px 12px; +} +.fork-group { text-align: left; } +.fork-group h4 { text-align: center; margin: 0 0 6px; font-size: 0.8rem; color: #c66; letter-spacing: 0.02em; } +.fork-group ul { margin: 0; padding-left: 16px; } +.fork-group li { font-size: 0.74rem; color: #999; line-height: 1.45; margin: 3px 0; } +.fork-more { font-size: 0.76rem; color: #777; margin: 0 0 6px; } +.fork-more a { color: #888; border-bottom: 1px solid rgba(136,136,136,0.3); } +.hs-inline { font-size: 0.8em; color: #e88; } +.reviewed { font-size: 0.7rem; color: #666; text-align: center; margin: -4px 0 10px; }