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
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.
* **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
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.
* [ ] **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)
### 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.
* **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.
@@ -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.*
```bash
spacetime publish --server maincloud ditchcord
spacetime publish --server maincloud zep
```
4. Generate the TypeScript bindings for the frontend:
```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:
```env
VITE_SPACETIMEDB_HOST=wss://maincloud.spacetimedb.com
VITE_SPACETIMEDB_DB_NAME=ditchcord
VITE_SPACETIMEDB_DB_NAME=zep
```
4. Run the development server:
```bash
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ditchcord</title>
<title>Zep</title>
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "ditchcord",
"name": "zep",
"private": true,
"version": "0.0.1",
"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",
"type": "module",
"scripts": {
+1 -1
View File
@@ -1225,7 +1225,7 @@ export const init = spacetimedb.init((ctx) => {
if (!hasServers) {
const s = ctx.db.server.insert({
id: 0n,
name: "Ditchcord",
name: "Zep",
owner: undefined,
});
ctx.db.channel.insert({
+12 -12
View File
@@ -713,18 +713,6 @@ dependencies = [
"syn 2.0.117",
]
[[package]]
name = "ditchcord"
version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
]
[[package]]
name = "dlopen2"
version = "0.8.2"
@@ -5193,6 +5181,18 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zep"
version = "0.1.0"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"tauri-build",
"tauri-plugin-log",
]
[[package]]
name = "zerocopy"
version = "0.8.48"
+3 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "ditchcord"
name = "zep"
version = "0.1.0"
description = "ditchcord - self-hosted communication"
description = "zep - self-hosted communication"
authors = ["Adam Lamers"]
license = ""
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
[lib]
name = "ditchcord_lib"
name = "zep_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
+1 -1
View File
@@ -2,5 +2,5 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
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",
"productName": "ditchcord",
"productName": "zep",
"version": "0.1.0",
"identifier": "com.ditchcord.chat",
"identifier": "com.zep.chat",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
@@ -12,7 +12,7 @@
"app": {
"windows": [
{
"title": "Ditchcord",
"title": "Zep",
"width": 800,
"height": 600,
"resizable": true,
+158 -37
View File
@@ -1,4 +1,114 @@
: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-secondary: #2b2d31;
--background-tertiary: #1e1f22;
@@ -10,23 +120,32 @@
--interactive-hover: #dbdee1;
--brand: #5865f2;
--brand-hover: #4752c4;
--server-sidebar-width: 72px;
--channel-sidebar-width: 240px;
--background-modifier-hover: rgba(78, 80, 88, 0.3);
--background-modifier-active: rgba(78, 80, 88, 0.6);
--background-modifier-selected: rgba(78, 80, 88, 0.6);
--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 {
margin: 0;
font-family: var(--font-primary);
@@ -173,7 +292,7 @@ body {
}
.server-dropdown-item.danger:hover {
background-color: #f23f43;
background-color: var(--status-danger);
}
.server-dropdown-item.muted {
@@ -249,13 +368,14 @@ body {
/* User Info Bar */
.user-info-bar {
background-color: #232428;
background-color: var(--background-tertiary);
padding: 0 8px;
display: flex;
align-items: center;
justify-content: space-between;
height: 52px;
flex-shrink: 0;
border-top: 1px solid rgba(255, 255, 255, 0.05);
}
.user-info-main {
@@ -290,7 +410,7 @@ body {
}
.avatar.talking {
border: 2px solid #23a559;
border: 2px solid var(--status-positive);
box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15);
}
@@ -365,7 +485,7 @@ body {
}
.avatar.talking {
border: 2px solid #23a559;
border: 2px solid var(--status-positive);
box-shadow: 0 0 0 2px rgba(35, 165, 89, 0.15);
}
@@ -448,7 +568,7 @@ body {
}
.icon-btn.danger.active {
color: #f23f43;
color: var(--status-danger);
}
/* Main Content Area */
@@ -903,7 +1023,7 @@ body {
}
.video-tile.talking {
border-color: #23a559;
border-color: var(--status-positive);
}
.video-tile video {
@@ -1107,7 +1227,7 @@ body {
}
.sharing-badge {
background-color: #f23f43;
background-color: var(--status-danger);
color: white;
font-size: 0.6rem;
padding: 1px 4px;
@@ -1152,18 +1272,18 @@ body {
}
.status-dot.green {
background-color: #23a559;
background-color: var(--status-positive);
}
.status-dot.yellow {
background-color: #f0b232;
background-color: var(--status-warning);
}
.status-dot.red {
background-color: #f23f43;
background-color: var(--status-danger);
}
.status-dot.grey {
background-color: #80848e;
background-color: var(--text-muted);
}
/* Connection Popover */
@@ -1212,13 +1332,13 @@ body {
}
.popover-status.green {
color: #23a559;
color: var(--status-positive);
}
.popover-status.yellow {
color: #f0b232;
color: var(--status-warning);
}
.popover-status.red {
color: #f23f43;
color: var(--status-danger);
}
.popover-info {
@@ -1379,7 +1499,7 @@ body {
}
.btn-danger {
background-color: #f23f43;
background-color: var(--status-danger);
color: white;
border: none;
padding: 8px 16px;
@@ -1393,7 +1513,7 @@ body {
}
.btn-danger:hover {
background-color: #d83c3e;
background-color: var(--status-danger);
}
.btn-danger.small {
@@ -1463,7 +1583,7 @@ body {
}
.login-error {
color: #fa777a;
color: var(--status-danger);
background-color: rgba(250, 119, 122, 0.1);
border: 1px solid rgba(250, 119, 122, 0.2);
padding: 8px;
@@ -1475,12 +1595,12 @@ body {
/* Voice Connected Bar */
.voice-status-bar {
background-color: #232428;
background-color: var(--background-tertiary);
padding: 8px;
display: flex;
justify-content: space-between;
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 {
@@ -1489,7 +1609,7 @@ body {
}
.voice-connected-text {
color: #23a559;
color: var(--status-positive);
font-size: 0.8rem;
font-weight: bold;
}
@@ -1500,7 +1620,7 @@ body {
}
.screen-share-btn {
background-color: #3f4147;
background-color: var(--background-accent);
color: white;
border: none;
padding: 4px 12px;
@@ -1512,15 +1632,15 @@ body {
}
.screen-share-btn:hover {
background-color: #4f5157;
background-color: var(--background-modifier-hover);
}
.screen-share-btn.active {
background-color: #f23f43;
background-color: var(--status-danger);
}
.screen-share-btn.active:hover {
background-color: #d83c3e;
opacity: 0.9;
}
.screen-share-controls {
@@ -1564,11 +1684,12 @@ body {
.context-menu {
position: fixed;
z-index: 3000;
background: #18191c;
background: var(--background-floating);
border-radius: 4px;
padding: 8px;
min-width: 180px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.24);
border: 1px solid rgba(255, 255, 255, 0.05);
}
.context-menu-header {
@@ -1599,7 +1720,7 @@ body {
}
.context-menu-item:hover {
background: var(--primary);
background: var(--brand);
color: white;
}
+10 -3
View File
@@ -2,15 +2,22 @@
import { AuthGate } from "./auth";
import { ChatContainer } from "./chat";
import { connectionState } from "./connection.svelte";
import { themeService } from "./chat/services/theme.svelte";
import "./App.css";
let showServerSettings = $state(false);
// Apply theme class to body globally
$effect(() => {
const theme = themeService.theme;
document.body.className = `theme-${theme}`;
});
function handleReconnect() {
console.log("App: Reconnection requested. Reloading page...");
// If we are showing settings, we should keep showing them after reload
if (showServerSettings) {
localStorage.setItem("ditchcord_changing_server", "true");
localStorage.setItem("zep_changing_server", "true");
}
window.location.reload();
}
@@ -19,9 +26,9 @@
console.log("App: Setting showServerSettings to", val);
showServerSettings = val;
if (val) {
localStorage.setItem("ditchcord_changing_server", "true");
localStorage.setItem("zep_changing_server", "true");
} else {
localStorage.removeItem("ditchcord_changing_server");
localStorage.removeItem("zep_changing_server");
}
}
</script>
+3 -3
View File
@@ -29,7 +29,7 @@
isMaincloud = stdbHost === MAINCLOUD_URI || stdbHost === "";
if (isMaincloud) stdbHost = MAINCLOUD_URI;
const isChanging = localStorage.getItem("ditchcord_changing_server") === "true";
const isChanging = localStorage.getItem("zep_changing_server") === "true";
if (isChanging) {
isSettingsExpanded = true;
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" />
</svg>
</div>
<h1>Ditchcord</h1>
<h1>Zep</h1>
<p class="tagline">Decentralized. Private. Fast.</p>
</div>
@@ -183,7 +183,7 @@
id="stdb-db"
type="text"
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;"
/>
</div>
+1 -1
View File
@@ -147,7 +147,7 @@
}
.status-dot.green { background-color: var(--status-positive); }
.status-dot.grey { background-color: #80848e; }
.status-dot.grey { background-color: var(--text-muted); }
.profile-info {
display: flex;
+1 -1
View File
@@ -29,7 +29,7 @@
<button
class="server-icon"
onclick={() => (chat.showDiscoveryModal = true)}
style="color: #23a559;"
style="color: var(--status-positive);"
title="Discover Servers"
>
<i class="fas fa-search"></i>
@@ -5,6 +5,16 @@
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 newEmojiFile = $state<File | null>(null);
let isEmojiUploading = $state(false);
@@ -42,6 +52,39 @@
</script>
<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">
<h3>Custom Emojis</h3>
<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;
}
.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 {
background-color: var(--background-secondary);
padding: 16px;
+3 -3
View File
@@ -5,7 +5,7 @@ import * as Types from "../../module_bindings/types";
import { getUsername, formatTime } from "../utils";
import { DatabaseService } from "./database.svelte";
import { NavigationService } from "./navigation.svelte";
import { UIService } from "./ui.svelte";
import { ThemeService, themeService } from "./theme.svelte";
import { AuthContextService } from "./auth-context.svelte";
import { MessagingService } from "./messaging.svelte";
import { VoiceService } from "./voice.svelte";
@@ -15,7 +15,7 @@ import { ServerManagementService } from "./server-management.svelte";
export class ChatService {
#db: DatabaseService;
#nav: NavigationService;
#ui: UIService;
#ui: ThemeService = themeService;
#auth: AuthContextService;
#msg: MessagingService;
#voice: VoiceService;
@@ -30,7 +30,7 @@ export class ChatService {
this.identity = initialIdentity;
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.#auth = new AuthContextService(this.#db, () => this.identity);
this.#msg = new MessagingService(this.#db, this.#nav, () => this.identity);
+1 -1
View File
@@ -95,7 +95,7 @@ export class MessagingService {
} else {
queries.push("SELECT * FROM custom_emoji");
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";
@@ -1,6 +1,6 @@
import { SvelteMap } from "svelte/reactivity";
export class UIService {
export class ThemeService {
showCreateServerModal = $state(false);
newServerName = $state("");
showCreateChannelModal = $state(false);
@@ -16,6 +16,16 @@ export class UIService {
viewingProfileUser = $state<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}`
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");
export const getStdbDbName = () =>
localStorage.getItem(DB_NAME_KEY) ||
getEnv("VITE_SPACETIMEDB_DB_NAME", "ditchcord");
getEnv("VITE_SPACETIMEDB_DB_NAME", "zep");
let _connection: DbConnection | null = null;
export const getConnection = () => _connection;
+6 -6
View File
@@ -9,17 +9,17 @@
/* ----- Color Variables ----- */
:root {
--theme-color: #3dc373;
--theme-color-contrast: #08180e;
--textbox-color: #edfef4;
--theme-color: #9d4edd;
--theme-color-contrast: #ffffff;
--textbox-color: #241b3d;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--theme-color: #4cf490;
--theme-color-contrast: #132219;
--textbox-color: #0f311d;
--theme-color: #9d4edd;
--theme-color-contrast: #ffffff;
--textbox-color: #0a0714;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@ import "@fortawesome/fontawesome-free/css/all.min.css";
import "./index.css";
import App from "./App.svelte";
console.log("Ditchcord: Starting app...");
console.log("Zep: Starting app...");
const app = mount(App, {
target: document.getElementById("root")!,
+1 -1
View File
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "ditchcord",
"name": "zep",
"compatibility_date": "2026-04-04",
"observability": {
"enabled": true,