Create sdks/web/ with a vanilla TypeScript web client: - IndexedDB local store for conversations, messages, and identity - WebSocket transport connecting to the server bridge - Service Worker with cache-first strategy for offline support - PWA manifest for installable web app - Dark-themed responsive UI with sidebar, messages, and input bar - Connection status badge and MLS epoch indicator in header - Unread message count badges on conversations
267 lines
4.2 KiB
CSS
267 lines
4.2 KiB
CSS
/* quicproquo web client — dark theme */
|
|
:root {
|
|
--bg: #0d1117;
|
|
--bg-surface: #161b22;
|
|
--bg-input: #21262d;
|
|
--fg: #c9d1d9;
|
|
--fg-muted: #8b949e;
|
|
--accent: #58a6ff;
|
|
--accent-dim: #1f6feb;
|
|
--green: #3fb950;
|
|
--red: #f85149;
|
|
--yellow: #d29922;
|
|
--border: #30363d;
|
|
--radius: 6px;
|
|
}
|
|
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
background: var(--bg);
|
|
color: var(--fg);
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
}
|
|
|
|
#app {
|
|
height: 100vh;
|
|
display: flex;
|
|
}
|
|
|
|
/* --- Login screen --- */
|
|
.screen { width: 100%; }
|
|
|
|
#login-screen {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
#login-screen h1 {
|
|
font-size: 2rem;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.subtitle {
|
|
color: var(--fg-muted);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
#login-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.75rem;
|
|
width: 320px;
|
|
}
|
|
|
|
input[type="text"],
|
|
input[type="password"] {
|
|
background: var(--bg-input);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
padding: 0.6rem 0.8rem;
|
|
color: var(--fg);
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
button {
|
|
background: var(--accent-dim);
|
|
color: var(--fg);
|
|
border: none;
|
|
border-radius: var(--radius);
|
|
padding: 0.6rem 1rem;
|
|
cursor: pointer;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
button:hover {
|
|
background: var(--accent);
|
|
color: var(--bg);
|
|
}
|
|
|
|
.error {
|
|
color: var(--red);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* --- Chat screen --- */
|
|
#chat-screen {
|
|
display: flex;
|
|
height: 100vh;
|
|
}
|
|
|
|
#sidebar {
|
|
width: 260px;
|
|
background: var(--bg-surface);
|
|
border-right: 1px solid var(--border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
#sidebar-header {
|
|
padding: 0.75rem;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
#user-display {
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
|
|
#logout-btn {
|
|
background: transparent;
|
|
color: var(--fg-muted);
|
|
font-size: 0.8rem;
|
|
padding: 0.3rem 0.5rem;
|
|
}
|
|
|
|
#conversation-list {
|
|
list-style: none;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
#conversation-list li {
|
|
padding: 0.6rem 0.75rem;
|
|
cursor: pointer;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
#conversation-list li:hover {
|
|
background: var(--bg-input);
|
|
}
|
|
|
|
#conversation-list li.active {
|
|
background: var(--bg-input);
|
|
border-left: 3px solid var(--accent);
|
|
}
|
|
|
|
.conv-name { flex: 1; }
|
|
|
|
.unread-badge {
|
|
background: var(--accent-dim);
|
|
color: var(--fg);
|
|
border-radius: 10px;
|
|
padding: 0.1rem 0.5rem;
|
|
font-size: 0.75rem;
|
|
min-width: 1.2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
#new-dm-btn {
|
|
margin: 0.5rem;
|
|
background: transparent;
|
|
border: 1px dashed var(--border);
|
|
color: var(--fg-muted);
|
|
}
|
|
|
|
/* --- Chat main area --- */
|
|
#chat-main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
#chat-header {
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid var(--border);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
}
|
|
|
|
#chat-title {
|
|
flex: 1;
|
|
font-weight: 600;
|
|
}
|
|
|
|
#epoch-badge {
|
|
font-size: 0.8rem;
|
|
color: var(--fg-muted);
|
|
background: var(--bg-input);
|
|
padding: 0.15rem 0.5rem;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
#conn-badge {
|
|
font-size: 0.8rem;
|
|
padding: 0.15rem 0.5rem;
|
|
border-radius: var(--radius);
|
|
}
|
|
|
|
#conn-badge.online {
|
|
background: var(--green);
|
|
color: var(--bg);
|
|
}
|
|
|
|
#conn-badge.offline {
|
|
background: var(--red);
|
|
color: var(--fg);
|
|
}
|
|
|
|
#messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.message {
|
|
max-width: 70%;
|
|
padding: 0.5rem 0.75rem;
|
|
border-radius: var(--radius);
|
|
background: var(--bg-surface);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
.message.outgoing {
|
|
align-self: flex-end;
|
|
background: var(--accent-dim);
|
|
border-color: var(--accent-dim);
|
|
}
|
|
|
|
.message .meta {
|
|
font-size: 0.75rem;
|
|
color: var(--fg-muted);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.message.outgoing .meta {
|
|
color: rgba(255, 255, 255, 0.6);
|
|
}
|
|
|
|
.message .body {
|
|
word-break: break-word;
|
|
}
|
|
|
|
#send-form {
|
|
display: flex;
|
|
padding: 0.5rem;
|
|
border-top: 1px solid var(--border);
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
#msg-input {
|
|
flex: 1;
|
|
}
|