theme select

This commit is contained in:
2026-04-05 18:40:35 -04:00
parent c17180be20
commit 0fc8dcb597
23 changed files with 374 additions and 90 deletions
+7 -7
View File
@@ -1,10 +1,10 @@
# Ditchcord # Zep
Ditchcord is a self-hosted, privacy-oriented chat application built with **Svelte 5** and **SpacetimeDB**. Designed as a modern, lightweight alternative to commercial platforms, it prioritizes performance, low-latency communication, and high feature density without the bloat. Zep is a self-hosted, privacy-oriented chat application built with **Svelte 5** and **SpacetimeDB**. Designed as a modern, lightweight alternative to commercial platforms, it prioritizes performance, low-latency communication, and high feature density without the bloat.
## 🚀 Features ## 🚀 Features
Ditchcord offers a rich, Discord-inspired user experience backed by a highly scalable, WebAssembly-powered database architecture. Zep offers a rich, Discord-inspired user experience backed by a highly scalable, WebAssembly-powered database architecture.
* **Multi-Server & Channel Support:** Organize your communities with dedicated Text, Voice, and Threaded channels. * **Multi-Server & Channel Support:** Organize your communities with dedicated Text, Voice, and Threaded channels.
* **Blazing Fast Pagination:** Uses a sophisticated hybrid "Recent Activity Cache" to load channels instantly, with on-demand expansion for deep history scrollback. * **Blazing Fast Pagination:** Uses a sophisticated hybrid "Recent Activity Cache" to load channels instantly, with on-demand expansion for deep history scrollback.
@@ -15,7 +15,7 @@ Ditchcord offers a rich, Discord-inspired user experience backed by a highly sca
## 🗺️ Roadmap ## 🗺️ Roadmap
We are actively developing Ditchcord. Here is what's coming next: We are actively developing Zep. Here is what's coming next:
* [ ] **Direct Messaging:** Private, 1-on-1 conversations outside of server contexts. * [ ] **Direct Messaging:** Private, 1-on-1 conversations outside of server contexts.
* [ ] **Mentions & Notifications:** Robust `@user` and `@role` tagging with RLS-protected notification delivery. * [ ] **Mentions & Notifications:** Robust `@user` and `@role` tagging with RLS-protected notification delivery.
@@ -32,7 +32,7 @@ We are actively developing Ditchcord. Here is what's coming next:
* **Desktop:** Tauri (Planned/Configured) * **Desktop:** Tauri (Planned/Configured)
### Architecture Highlights ### Architecture Highlights
Ditchcord leverages **SpacetimeDB's client-side cache model** to eliminate traditional REST API bottlenecks. Zep leverages **SpacetimeDB's client-side cache model** to eliminate traditional REST API bottlenecks.
* **Zero-Latency Switching:** The client subscribes to a lightweight `recent_message` table for all joined servers on startup. This populates the UI instantly when switching channels. * **Zero-Latency Switching:** The client subscribes to a lightweight `recent_message` table for all joined servers on startup. This populates the UI instantly when switching channels.
* **Surgical Subscriptions:** The heavy `message` history table is only subscribed to on-demand when a user explicitly requests older messages, saving bandwidth and memory. * **Surgical Subscriptions:** The heavy `message` history table is only subscribed to on-demand when a user explicitly requests older messages, saving bandwidth and memory.
* **WASM Reducers:** All database mutations (sending messages, updating profiles) are handled by deterministic TypeScript reducers compiled to WebAssembly and executed transactionally on the server. * **WASM Reducers:** All database mutations (sending messages, updating profiles) are handled by deterministic TypeScript reducers compiled to WebAssembly and executed transactionally on the server.
@@ -56,7 +56,7 @@ Ditchcord leverages **SpacetimeDB's client-side cache model** to eliminate tradi
``` ```
3. Publish the module to your SpacetimeDB instance (e.g., `maincloud` or a local instance). *Note: The first publish creates the database.* 3. Publish the module to your SpacetimeDB instance (e.g., `maincloud` or a local instance). *Note: The first publish creates the database.*
```bash ```bash
spacetime publish --server maincloud ditchcord spacetime publish --server maincloud zep
``` ```
4. Generate the TypeScript bindings for the frontend: 4. Generate the TypeScript bindings for the frontend:
```bash ```bash
@@ -76,7 +76,7 @@ Ditchcord leverages **SpacetimeDB's client-side cache model** to eliminate tradi
3. Configure your environment variables (create a `.env.local` file if necessary) to point to your published SpacetimeDB instance: 3. Configure your environment variables (create a `.env.local` file if necessary) to point to your published SpacetimeDB instance:
```env ```env
VITE_SPACETIMEDB_HOST=wss://maincloud.spacetimedb.com VITE_SPACETIMEDB_HOST=wss://maincloud.spacetimedb.com
VITE_SPACETIMEDB_DB_NAME=ditchcord VITE_SPACETIMEDB_DB_NAME=zep
``` ```
4. Run the development server: 4. Run the development server:
```bash ```bash
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ditchcord</title> <title>Zep</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "ditchcord", "name": "zep",
"private": true, "private": true,
"version": "0.0.1", "version": "0.0.1",
"type": "module", "type": "module",
+15 -1
View File
@@ -1 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<defs>
<linearGradient id="zepGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#c77dff" />
<stop offset="100%" stop-color="#7b2cbf" />
</linearGradient>
<filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<rect width="256" height="256" rx="64" fill="#0a0714" />
<path d="M60 70 L196 70 L110 186 L196 186" fill="none" stroke="url(#zepGrad)" stroke-width="32" stroke-linecap="round" stroke-linejoin="round" filter="url(#glow)" />
<path d="M60 70 L196 70 L110 186 L196 186" fill="none" stroke="#ffffff" stroke-width="12" stroke-linecap="round" stroke-linejoin="round" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 853 B

+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "ditchcord", "name": "zep",
"license": "ISC", "license": "ISC",
"type": "module", "type": "module",
"scripts": { "scripts": {
+1 -1
View File
@@ -1225,7 +1225,7 @@ export const init = spacetimedb.init((ctx) => {
if (!hasServers) { if (!hasServers) {
const s = ctx.db.server.insert({ const s = ctx.db.server.insert({
id: 0n, id: 0n,
name: "Ditchcord", name: "Zep",
owner: undefined, owner: undefined,
}); });
ctx.db.channel.insert({ ctx.db.channel.insert({
+12 -12
View File
@@ -713,18 +713,6 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "ditchcord"
version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
]
[[package]] [[package]]
name = "dlopen2" name = "dlopen2"
version = "0.8.2" version = "0.8.2"
@@ -5193,6 +5181,18 @@ dependencies = [
"synstructure", "synstructure",
] ]
[[package]]
name = "zep"
version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.48" version = "0.8.48"
+3 -3
View File
@@ -1,7 +1,7 @@
[package] [package]
name = "ditchcord" name = "zep"
version = "0.1.0" version = "0.1.0"
description = "ditchcord - self-hosted communication" description = "zep - self-hosted communication"
authors = ["Adam Lamers"] authors = ["Adam Lamers"]
license = "" license = ""
repository = "" repository = ""
@@ -11,7 +11,7 @@ rust-version = "1.77.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib] [lib]
name = "ditchcord_lib" name = "zep_lib"
crate-type = ["staticlib", "cdylib", "rlib"] crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies] [build-dependencies]
+1 -1
View File
@@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() { fn main() {
ditchcord_lib::run(); zep_lib::run();
} }
+3 -3
View File
@@ -1,8 +1,8 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "ditchcord", "productName": "zep",
"version": "0.1.0", "version": "0.1.0",
"identifier": "com.ditchcord.chat", "identifier": "com.zep.chat",
"build": { "build": {
"frontendDist": "../dist", "frontendDist": "../dist",
"devUrl": "http://localhost:5173", "devUrl": "http://localhost:5173",
@@ -12,7 +12,7 @@
"app": { "app": {
"windows": [ "windows": [
{ {
"title": "Ditchcord", "title": "Zep",
"width": 800, "width": 800,
"height": 600, "height": 600,
"resizable": true, "resizable": true,
+158 -37
View File
@@ -1,4 +1,114 @@
:root { :root {
--server-sidebar-width: 72px;
--channel-sidebar-width: 240px;
--font-primary:
"gg sans", "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-display:
"gg sans", "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-code:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
--status-positive: var(--status-positive);
--status-danger: var(--status-danger);
--status-warning: var(--status-warning);
}
/* Premium Midnight Purple (Amethyst) */
.theme-amethyst, :root {
--background-primary: #161126;
--background-secondary: #0f0a1a;
--background-tertiary: #0a0714;
--background-accent: #241b3d;
--header-primary: #f3eeff;
--text-normal: #e0d1f0;
--text-muted: #9a8cbb;
--interactive-normal: #b3a5d1;
--interactive-hover: #ffffff;
--brand: #9d4edd;
--brand-hover: #7b2cbf;
--background-modifier-hover: rgba(157, 78, 221, 0.15);
--background-modifier-active: rgba(157, 78, 221, 0.3);
--background-modifier-selected: rgba(157, 78, 221, 0.3);
--background-floating: #0a0714;
}
/* Slate Cyan (Cyber) */
.theme-cyan {
--background-primary: #121826;
--background-secondary: #0c111a;
--background-tertiary: #080c13;
--background-accent: #1f2937;
--header-primary: #f8fafc;
--text-normal: #cbd5e1;
--text-muted: #64748b;
--interactive-normal: #94a3b8;
--interactive-hover: #f1f5f9;
--brand: #00d2ff;
--brand-hover: #00a8cc;
--background-modifier-hover: rgba(0, 210, 255, 0.15);
--background-modifier-active: rgba(0, 210, 255, 0.3);
--background-modifier-selected: rgba(0, 210, 255, 0.3);
--background-floating: #080c13;
}
/* Deep Sea Green (Emerald) */
.theme-emerald {
--background-primary: #061a14;
--background-secondary: #04120e;
--background-tertiary: #020a08;
--background-accent: #0d2e24;
--header-primary: #e6fffa;
--text-normal: #b2f5ea;
--text-muted: #4e9a84;
--interactive-normal: #81e6d9;
--interactive-hover: #ffffff;
--brand: #10b981;
--brand-hover: #059669;
--background-modifier-hover: rgba(16, 185, 129, 0.15);
--background-modifier-active: rgba(16, 185, 129, 0.3);
--background-modifier-selected: rgba(16, 185, 129, 0.3);
--background-floating: #020a08;
}
/* Crimson Rose (Rose) */
.theme-rose {
--background-primary: #1a0a0f;
--background-secondary: #12060a;
--background-tertiary: #0a0305;
--background-accent: #2e0d16;
--header-primary: #fff5f7;
--text-normal: #fed7e2;
--text-muted: #9b4e64;
--interactive-normal: #fbb6ce;
--interactive-hover: #ffffff;
--brand: #f43f5e;
--brand-hover: #e11d48;
--background-modifier-hover: rgba(244, 63, 94, 0.15);
--background-modifier-active: rgba(244, 63, 94, 0.3);
--background-modifier-selected: rgba(244, 63, 94, 0.3);
--background-floating: #0a0305;
}
/* Amber Gold (Amber) */
.theme-amber {
--background-primary: #1a1405;
--background-secondary: #120e03;
--background-tertiary: #0a0802;
--background-accent: #2e240d;
--header-primary: #fffaf0;
--text-normal: #fef3c7;
--text-muted: #9a7b4e;
--interactive-normal: #fcd34d;
--interactive-hover: #ffffff;
--brand: #f59e0b;
--brand-hover: #d97706;
--background-modifier-hover: rgba(245, 158, 11, 0.15);
--background-modifier-active: rgba(245, 158, 11, 0.3);
--background-modifier-selected: rgba(245, 158, 11, 0.3);
--background-floating: #0a0802;
}
/* Generic Dark (Standard) */
.theme-dark {
--background-primary: #313338; --background-primary: #313338;
--background-secondary: #2b2d31; --background-secondary: #2b2d31;
--background-tertiary: #1e1f22; --background-tertiary: #1e1f22;
@@ -10,23 +120,32 @@
--interactive-hover: #dbdee1; --interactive-hover: #dbdee1;
--brand: #5865f2; --brand: #5865f2;
--brand-hover: #4752c4; --brand-hover: #4752c4;
--server-sidebar-width: 72px;
--channel-sidebar-width: 240px;
--background-modifier-hover: rgba(78, 80, 88, 0.3); --background-modifier-hover: rgba(78, 80, 88, 0.3);
--background-modifier-active: rgba(78, 80, 88, 0.6); --background-modifier-active: rgba(78, 80, 88, 0.6);
--background-modifier-selected: rgba(78, 80, 88, 0.6); --background-modifier-selected: rgba(78, 80, 88, 0.6);
--background-floating: #1e1f22; --background-floating: #1e1f22;
--status-positive: #23a559;
--status-danger: #f23f43;
--status-warning: #f0b232;
--font-primary:
"gg sans", "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-display:
"gg sans", "Noto Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
--font-code:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
} }
/* Generic Light (Standard) */
.theme-light {
--background-primary: #ffffff;
--background-secondary: #f2f3f5;
--background-tertiary: #e3e5e8;
--background-accent: #ebedef;
--header-primary: #060607;
--text-normal: #313338;
--text-muted: #5c6067;
--interactive-normal: #4e5058;
--interactive-hover: #060607;
--brand: #5865f2;
--brand-hover: #4752c4;
--background-modifier-hover: rgba(78, 80, 88, 0.1);
--background-modifier-active: rgba(78, 80, 88, 0.2);
--background-modifier-selected: rgba(78, 80, 88, 0.2);
--background-floating: #ffffff;
}
body { body {
margin: 0; margin: 0;
font-family: var(--font-primary); font-family: var(--font-primary);
@@ -173,7 +292,7 @@ body {
} }
.server-dropdown-item.danger:hover { .server-dropdown-item.danger:hover {
background-color: #f23f43; background-color: var(--status-danger);
} }
.server-dropdown-item.muted { .server-dropdown-item.muted {
@@ -249,13 +368,14 @@ body {
/* User Info Bar */ /* User Info Bar */
.user-info-bar { .user-info-bar {
background-color: #232428; background-color: var(--background-tertiary);
padding: 0 8px; padding: 0 8px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
height: 52px; height: 52px;
flex-shrink: 0; flex-shrink: 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
} }
.user-info-main { .user-info-main {
@@ -290,7 +410,7 @@ body {
} }
.avatar.talking { .avatar.talking {
border: 2px solid #23a559; border: 2px solid var(--status-positive);
box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15); box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15);
} }
@@ -365,7 +485,7 @@ body {
} }
.avatar.talking { .avatar.talking {
border: 2px solid #23a559; border: 2px solid var(--status-positive);
box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15); box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15);
} }
@@ -448,7 +568,7 @@ body {
} }
.icon-btn.danger.active { .icon-btn.danger.active {
color: #f23f43; color: var(--status-danger);
} }
/* Main Content Area */ /* Main Content Area */
@@ -903,7 +1023,7 @@ body {
} }
.video-tile.talking { .video-tile.talking {
border-color: #23a559; border-color: var(--status-positive);
} }
.video-tile video { .video-tile video {
@@ -1107,7 +1227,7 @@ body {
} }
.sharing-badge { .sharing-badge {
background-color: #f23f43; background-color: var(--status-danger);
color: white; color: white;
font-size: 0.6rem; font-size: 0.6rem;
padding: 1px 4px; padding: 1px 4px;
@@ -1152,18 +1272,18 @@ body {
} }
.status-dot.green { .status-dot.green {
background-color: #23a559; background-color: var(--status-positive);
} }
.status-dot.yellow { .status-dot.yellow {
background-color: #f0b232; background-color: var(--status-warning);
} }
.status-dot.red { .status-dot.red {
background-color: #f23f43; background-color: var(--status-danger);
} }
.status-dot.grey { .status-dot.grey {
background-color: #80848e; background-color: var(--text-muted);
} }
/* Connection Popover */ /* Connection Popover */
@@ -1212,13 +1332,13 @@ body {
} }
.popover-status.green { .popover-status.green {
color: #23a559; color: var(--status-positive);
} }
.popover-status.yellow { .popover-status.yellow {
color: #f0b232; color: var(--status-warning);
} }
.popover-status.red { .popover-status.red {
color: #f23f43; color: var(--status-danger);
} }
.popover-info { .popover-info {
@@ -1379,7 +1499,7 @@ body {
} }
.btn-danger { .btn-danger {
background-color: #f23f43; background-color: var(--status-danger);
color: white; color: white;
border: none; border: none;
padding: 8px 16px; padding: 8px 16px;
@@ -1393,7 +1513,7 @@ body {
} }
.btn-danger:hover { .btn-danger:hover {
background-color: #d83c3e; background-color: var(--status-danger);
} }
.btn-danger.small { .btn-danger.small {
@@ -1463,7 +1583,7 @@ body {
} }
.login-error { .login-error {
color: #fa777a; color: var(--status-danger);
background-color: rgba(250, 119, 122, 0.1); background-color: rgba(250, 119, 122, 0.1);
border: 1px solid rgba(250, 119, 122, 0.2); border: 1px solid rgba(250, 119, 122, 0.2);
padding: 8px; padding: 8px;
@@ -1475,12 +1595,12 @@ body {
/* Voice Connected Bar */ /* Voice Connected Bar */
.voice-status-bar { .voice-status-bar {
background-color: #232428; background-color: var(--background-tertiary);
padding: 8px; padding: 8px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-top: 1px solid rgba(255, 255, 255, 0.1); border-top: 1px solid rgba(255, 255, 255, 0.05);
} }
.voice-info { .voice-info {
@@ -1489,7 +1609,7 @@ body {
} }
.voice-connected-text { .voice-connected-text {
color: #23a559; color: var(--status-positive);
font-size: 0.8rem; font-size: 0.8rem;
font-weight: bold; font-weight: bold;
} }
@@ -1500,7 +1620,7 @@ body {
} }
.screen-share-btn { .screen-share-btn {
background-color: #3f4147; background-color: var(--background-accent);
color: white; color: white;
border: none; border: none;
padding: 4px 12px; padding: 4px 12px;
@@ -1512,15 +1632,15 @@ body {
} }
.screen-share-btn:hover { .screen-share-btn:hover {
background-color: #4f5157; background-color: var(--background-modifier-hover);
} }
.screen-share-btn.active { .screen-share-btn.active {
background-color: #f23f43; background-color: var(--status-danger);
} }
.screen-share-btn.active:hover { .screen-share-btn.active:hover {
background-color: #d83c3e; opacity: 0.9;
} }
.screen-share-controls { .screen-share-controls {
@@ -1564,11 +1684,12 @@ body {
.context-menu { .context-menu {
position: fixed; position: fixed;
z-index: 3000; z-index: 3000;
background: #18191c; background: var(--background-floating);
border-radius: 4px; border-radius: 4px;
padding: 8px; padding: 8px;
min-width: 180px; min-width: 180px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
border: 1px solid rgba(255, 255, 255, 0.05);
} }
.context-menu-header { .context-menu-header {
@@ -1599,7 +1720,7 @@ body {
} }
.context-menu-item:hover { .context-menu-item:hover {
background: var(--primary); background: var(--brand);
color: white; color: white;
} }
+10 -3
View File
@@ -2,15 +2,22 @@
import { AuthGate } from "./auth"; import { AuthGate } from "./auth";
import { ChatContainer } from "./chat"; import { ChatContainer } from "./chat";
import { connectionState } from "./connection.svelte"; import { connectionState } from "./connection.svelte";
import { themeService } from "./chat/services/theme.svelte";
import "./App.css"; import "./App.css";
let showServerSettings = $state(false); let showServerSettings = $state(false);
// Apply theme class to body globally
$effect(() => {
const theme = themeService.theme;
document.body.className = `theme-${theme}`;
});
function handleReconnect() { function handleReconnect() {
console.log("App: Reconnection requested. Reloading page..."); console.log("App: Reconnection requested. Reloading page...");
// If we are showing settings, we should keep showing them after reload // If we are showing settings, we should keep showing them after reload
if (showServerSettings) { if (showServerSettings) {
localStorage.setItem("ditchcord_changing_server", "true"); localStorage.setItem("zep_changing_server", "true");
} }
window.location.reload(); window.location.reload();
} }
@@ -19,9 +26,9 @@
console.log("App: Setting showServerSettings to", val); console.log("App: Setting showServerSettings to", val);
showServerSettings = val; showServerSettings = val;
if (val) { if (val) {
localStorage.setItem("ditchcord_changing_server", "true"); localStorage.setItem("zep_changing_server", "true");
} else { } else {
localStorage.removeItem("ditchcord_changing_server"); localStorage.removeItem("zep_changing_server");
} }
} }
</script> </script>
+3 -3
View File
@@ -29,7 +29,7 @@
isMaincloud = stdbHost === MAINCLOUD_URI || stdbHost === ""; isMaincloud = stdbHost === MAINCLOUD_URI || stdbHost === "";
if (isMaincloud) stdbHost = MAINCLOUD_URI; if (isMaincloud) stdbHost = MAINCLOUD_URI;
const isChanging = localStorage.getItem("ditchcord_changing_server") === "true"; const isChanging = localStorage.getItem("zep_changing_server") === "true";
if (isChanging) { if (isChanging) {
isSettingsExpanded = true; isSettingsExpanded = true;
userWantsToConnect = false; userWantsToConnect = false;
@@ -95,7 +95,7 @@
<path d="M30 20 Q70 20 70 50 Q70 80 30 80" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round" class="logo-d" /> <path d="M30 20 Q70 20 70 50 Q70 80 30 80" fill="none" stroke="currentColor" stroke-width="8" stroke-linecap="round" class="logo-d" />
</svg> </svg>
</div> </div>
<h1>Ditchcord</h1> <h1>Zep</h1>
<p class="tagline">Decentralized. Private. Fast.</p> <p class="tagline">Decentralized. Private. Fast.</p>
</div> </div>
@@ -183,7 +183,7 @@
id="stdb-db" id="stdb-db"
type="text" type="text"
bind:value={stdbDbName} bind:value={stdbDbName}
placeholder="ditchcord" placeholder="zep"
style="background-color: var(--background-tertiary); color: var(--text-normal); border: 1px solid var(--background-modifier-accent); border-radius: 4px; padding: 10px;" style="background-color: var(--background-tertiary); color: var(--text-normal); border: 1px solid var(--background-modifier-accent); border-radius: 4px; padding: 10px;"
/> />
</div> </div>
+1 -1
View File
@@ -147,7 +147,7 @@
} }
.status-dot.green { background-color: var(--status-positive); } .status-dot.green { background-color: var(--status-positive); }
.status-dot.grey { background-color: #80848e; } .status-dot.grey { background-color: var(--text-muted); }
.profile-info { .profile-info {
display: flex; display: flex;
+1 -1
View File
@@ -29,7 +29,7 @@
<button <button
class="server-icon" class="server-icon"
onclick={() => (chat.showDiscoveryModal = true)} onclick={() => (chat.showDiscoveryModal = true)}
style="color: #23a559;" style="color: var(--status-positive);"
title="Discover Servers" title="Discover Servers"
> >
<i class="fas fa-search"></i> <i class="fas fa-search"></i>
@@ -5,6 +5,16 @@
const chat = getContext<ChatService>("chat"); const chat = getContext<ChatService>("chat");
const themes = [
{ id: "dark", name: "Dark", color: "#5865f2", bg: "#313338" },
{ id: "light", name: "Light", color: "#5865f2", bg: "#ffffff" },
{ id: "amethyst", name: "Amethyst", color: "#9d4edd", bg: "#0f0a1a" },
{ id: "cyan", name: "Cyber Cyan", color: "#00d2ff", bg: "#0c111a" },
{ id: "emerald", name: "Emerald Sea", color: "#10b981", bg: "#04120e" },
{ id: "rose", name: "Crimson Rose", color: "#f43f5e", bg: "#12060a" },
{ id: "amber", name: "Amber Gold", color: "#f59e0b", bg: "#120e03" },
];
let newEmojiName = $state(""); let newEmojiName = $state("");
let newEmojiFile = $state<File | null>(null); let newEmojiFile = $state<File | null>(null);
let isEmojiUploading = $state(false); let isEmojiUploading = $state(false);
@@ -42,6 +52,39 @@
</script> </script>
<div class="section"> <div class="section">
<div class="section-header-description">
<h3>Appearance</h3>
<p>Choose a color scheme that fits your style.</p>
</div>
<div class="theme-selection-container">
{#each themes as theme}
<button
class="theme-card"
class:active={chat.ui.theme === theme.id}
onclick={() => chat.ui.setTheme(theme.id)}
style="--theme-accent: {theme.color}; --theme-bg: {theme.bg};"
>
<div class="theme-preview">
<div class="theme-preview-sidebar"></div>
<div class="theme-preview-content">
<div class="theme-preview-header"></div>
<div class="theme-preview-msg"></div>
<div class="theme-preview-msg short"></div>
</div>
</div>
<span class="theme-name">{theme.name}</span>
{#if chat.ui.theme === theme.id}
<div class="theme-active-check">
<i class="fas fa-check-circle"></i>
</div>
{/if}
</button>
{/each}
</div>
</div>
<div class="section" style="margin-top: 32px;">
<div class="section-header-description"> <div class="section-header-description">
<h3>Custom Emojis</h3> <h3>Custom Emojis</h3>
<p>Personalize your experience by uploading unique emojis. They'll be automatically optimized for the best performance.</p> <p>Personalize your experience by uploading unique emojis. They'll be automatically optimized for the best performance.</p>
@@ -120,6 +163,95 @@
line-height: 1.4; line-height: 1.4;
} }
.theme-selection-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 16px;
margin-top: 16px;
}
.theme-card {
background-color: var(--background-secondary);
border: 2px solid transparent;
border-radius: 8px;
padding: 12px;
cursor: pointer;
display: flex;
flex-direction: column;
gap: 12px;
transition: all 0.2s;
position: relative;
text-align: left;
}
.theme-card:hover {
background-color: var(--background-modifier-hover);
border-color: var(--background-modifier-accent);
}
.theme-card.active {
border-color: var(--theme-accent);
background-color: var(--background-modifier-selected);
}
.theme-preview {
height: 60px;
background-color: var(--theme-bg);
border-radius: 4px;
display: flex;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.05);
}
.theme-preview-sidebar {
width: 20px;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
border-right: 1px solid rgba(255, 255, 255, 0.05);
}
.theme-preview-content {
flex: 1;
padding: 6px;
display: flex;
flex-direction: column;
gap: 4px;
}
.theme-preview-header {
height: 6px;
width: 60%;
background-color: var(--theme-accent);
border-radius: 2px;
margin-bottom: 4px;
opacity: 0.8;
}
.theme-preview-msg {
height: 4px;
width: 90%;
background-color: rgba(255, 255, 255, 0.1);
border-radius: 2px;
}
.theme-preview-msg.short {
width: 50%;
}
.theme-name {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-normal);
}
.theme-active-check {
position: absolute;
top: 8px;
right: 8px;
color: var(--theme-accent);
font-size: 1.1rem;
}
.emoji-management-container { .emoji-management-container {
background-color: var(--background-secondary); background-color: var(--background-secondary);
padding: 16px; padding: 16px;
+3 -3
View File
@@ -5,7 +5,7 @@ import * as Types from "../../module_bindings/types";
import { getUsername, formatTime } from "../utils"; import { getUsername, formatTime } from "../utils";
import { DatabaseService } from "./database.svelte"; import { DatabaseService } from "./database.svelte";
import { NavigationService } from "./navigation.svelte"; import { NavigationService } from "./navigation.svelte";
import { UIService } from "./ui.svelte"; import { ThemeService, themeService } from "./theme.svelte";
import { AuthContextService } from "./auth-context.svelte"; import { AuthContextService } from "./auth-context.svelte";
import { MessagingService } from "./messaging.svelte"; import { MessagingService } from "./messaging.svelte";
import { VoiceService } from "./voice.svelte"; import { VoiceService } from "./voice.svelte";
@@ -15,7 +15,7 @@ import { ServerManagementService } from "./server-management.svelte";
export class ChatService { export class ChatService {
#db: DatabaseService; #db: DatabaseService;
#nav: NavigationService; #nav: NavigationService;
#ui: UIService; #ui: ThemeService = themeService;
#auth: AuthContextService; #auth: AuthContextService;
#msg: MessagingService; #msg: MessagingService;
#voice: VoiceService; #voice: VoiceService;
@@ -30,7 +30,7 @@ export class ChatService {
this.identity = initialIdentity; this.identity = initialIdentity;
this.#db = new DatabaseService(); this.#db = new DatabaseService();
this.#ui = new UIService(); // this.#ui = new ThemeService(); // Removed to use shared themeService
this.#nav = new NavigationService(this.#db, () => this.identity); this.#nav = new NavigationService(this.#db, () => this.identity);
this.#auth = new AuthContextService(this.#db, () => this.identity); this.#auth = new AuthContextService(this.#db, () => this.identity);
this.#msg = new MessagingService(this.#db, this.#nav, () => this.identity); this.#msg = new MessagingService(this.#db, this.#nav, () => this.identity);
+1 -1
View File
@@ -95,7 +95,7 @@ export class MessagingService {
} else { } else {
queries.push("SELECT * FROM custom_emoji"); queries.push("SELECT * FROM custom_emoji");
queries.push("SELECT * FROM system_configuration"); queries.push("SELECT * FROM system_configuration");
queries.push("SELECT * FROM server WHERE name = 'Ditchcord'"); queries.push("SELECT * FROM server WHERE name = 'Zep'");
} }
let userQuery = "SELECT * FROM user WHERE online = true"; let userQuery = "SELECT * FROM user WHERE online = true";
@@ -1,6 +1,6 @@
import { SvelteMap } from "svelte/reactivity"; import { SvelteMap } from "svelte/reactivity";
export class UIService { export class ThemeService {
showCreateServerModal = $state(false); showCreateServerModal = $state(false);
newServerName = $state(""); newServerName = $state("");
showCreateChannelModal = $state(false); showCreateChannelModal = $state(false);
@@ -16,6 +16,16 @@ export class UIService {
viewingProfileUser = $state<any | null>(null); viewingProfileUser = $state<any | null>(null);
userContextMenu = $state<{ x: number, y: number, user: any } | null>(null); userContextMenu = $state<{ x: number, y: number, user: any } | null>(null);
// Theme management
theme = $state(localStorage.getItem("zep_theme") || "dark");
setTheme(newTheme: string) {
this.theme = newTheme;
localStorage.setItem("zep_theme", newTheme);
}
// Track collapsed state of embeds by key: `${messageId}-${index}` // Track collapsed state of embeds by key: `${messageId}-${index}`
embedCollapsedStates = new SvelteMap<string, boolean>(); embedCollapsedStates = new SvelteMap<string, boolean>();
} }
export const themeService = new ThemeService();
+1 -1
View File
@@ -41,7 +41,7 @@ export const getStdbHost = () =>
getEnv("VITE_SPACETIMEDB_HOST", "wss://maincloud.spacetimedb.com"); getEnv("VITE_SPACETIMEDB_HOST", "wss://maincloud.spacetimedb.com");
export const getStdbDbName = () => export const getStdbDbName = () =>
localStorage.getItem(DB_NAME_KEY) || localStorage.getItem(DB_NAME_KEY) ||
getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord"); getEnv("VITE_SPACETIMEDB_DB_NAME", "zep");
let _connection: DbConnection | null = null; let _connection: DbConnection | null = null;
export const getConnection = () => _connection; export const getConnection = () => _connection;
+6 -6
View File
@@ -9,17 +9,17 @@
/* ----- Color Variables ----- */ /* ----- Color Variables ----- */
:root { :root {
--theme-color: #3dc373; --theme-color: #9d4edd;
--theme-color-contrast: #08180e; --theme-color-contrast: #ffffff;
--textbox-color: #edfef4; --textbox-color: #241b3d;
color-scheme: light dark; color-scheme: light dark;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--theme-color: #4cf490; --theme-color: #9d4edd;
--theme-color-contrast: #132219; --theme-color-contrast: #ffffff;
--textbox-color: #0f311d; --textbox-color: #0a0714;
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import "@fortawesome/fontawesome-free/css/all.min.css";
import "./index.css"; import "./index.css";
import App from "./App.svelte"; import App from "./App.svelte";
console.log("Ditchcord: Starting app..."); console.log("Zep: Starting app...");
const app = mount(App, { const app = mount(App, {
target: document.getElementById("root")!, target: document.getElementById("root")!,
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"$schema": "node_modules/wrangler/config-schema.json", "$schema": "node_modules/wrangler/config-schema.json",
"name": "ditchcord", "name": "zep",
"compatibility_date": "2026-04-04", "compatibility_date": "2026-04-04",
"observability": { "observability": {
"enabled": true, "enabled": true,