feat(web): add browser web client with IndexedDB, Service Worker, and PWA
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
This commit is contained in:
266
sdks/web/public/style.css
Normal file
266
sdks/web/public/style.css
Normal file
@@ -0,0 +1,266 @@
|
||||
/* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user