Files
hyprcrd/public/plugins/chat/chat.css
T
thandClaude Opus 4.6 9267c7c6a1 chat: group chat UI — room tabs, creation flow, member mgmt
Generalises the flyout from peer-only to a thread abstraction
({kind: 'peer'|'room', key, label}). Conversations list and peer
strip render rooms with a group icon alongside 1:1 peers; clicking
either opens the same thread view.

New UI surface:
  - Pick view gets a 1:1 / group mode switch. Group mode reveals a
    name field + multi-select member list and a Create button.
  - Room threads show an info icon in the flyout header that opens a
    member list; owners can add/remove members, rename-later, or
    delete the room; non-owners can leave.
  - 1:1 threads show a disposable toggle (ph-fire) in the header;
    flipping it tells the relay to drop future sends after delivery
    instead of persisting.
  - Group bubbles show the author's username above each message when
    it changes, matching common chat UX.

Thread open triggers a backfill fetch so cold caches / second
devices catch up on older history from the relay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 20:15:50 +02:00

540 lines
12 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ── Chat plugin styles ── */
#chat-flyout {
position: fixed;
bottom: 48px;
right: 16px;
width: 340px;
height: 460px;
max-height: calc(100vh - 100px);
background: var(--t-surface);
border: 1px solid var(--t-border);
border-radius: var(--ui-radius-modal);
box-shadow: var(--ui-shadow-modal);
z-index: 100;
display: flex;
flex-direction: column;
overflow: hidden;
}
#chat-flyout.hidden { display: none; }
#chat-flyout-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
.chat-view {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.chat-view.hidden { display: none; }
/* ── Conversation list ── */
.chat-list {
flex: 1;
overflow-y: auto;
}
/* When empty, collapse so the `.chat-empty` placeholder can fill and center
properly instead of being pushed into the lower half of the view. */
.chat-list:empty {
flex: 0 0 0;
display: none;
}
.chat-list-row {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid var(--t-border);
cursor: pointer;
transition: background 0.1s;
}
.chat-list-row:hover {
background: var(--t-raised);
}
.chat-list-row .chat-list-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--t-raised);
display: flex;
align-items: center;
justify-content: center;
color: var(--t-accent);
flex-shrink: 0;
}
.chat-list-row .chat-list-info {
flex: 1;
min-width: 0;
}
.chat-list-row .chat-list-peer {
font-family: var(--t-font-pixel);
font-size: 10px;
color: var(--t-bright);
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 6px;
}
.chat-list-row .chat-list-time {
font-size: 9px;
color: var(--t-dim);
flex-shrink: 0;
}
.chat-list-row .chat-list-preview {
font-size: 10px;
color: var(--t-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-top: 2px;
}
.chat-list-row .chat-list-preview.outgoing::before {
content: "› ";
color: var(--t-dim);
}
.chat-list-row .chat-unread-badge {
background: var(--t-accent);
color: var(--t-bg);
font-family: var(--t-font-pixel);
font-size: 9px;
padding: 1px 5px;
border-radius: 8px;
flex-shrink: 0;
}
.chat-empty {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px;
user-select: none;
-webkit-user-select: none;
}
.chat-empty > p {
margin: 0;
}
#chat-empty-start {
margin-top: 20px;
}
/* ── Thread view ── */
/* Horizontal peer strip above the thread header. Lets the user hop
between conversations without bouncing back to the list. Scrollbar
hidden — wheel-over scrolls horizontally (see chat.js). */
.chat-thread-peers {
display: flex;
gap: 5px;
padding: 6px 8px;
border-bottom: 1px solid var(--t-border);
background: var(--t-bg);
overflow-x: auto;
overflow-y: hidden;
flex-shrink: 0;
scrollbar-width: none;
-ms-overflow-style: none;
}
.chat-thread-peers::-webkit-scrollbar { display: none; }
.chat-thread-peers:empty { display: none; }
.chat-peer-tab {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 8px;
border: 1px solid var(--t-border);
border-radius: var(--ui-radius-button, 3px);
background: var(--t-surface);
color: var(--t-muted);
font-family: var(--t-font-pixel);
font-size: 10px;
cursor: pointer;
flex-shrink: 0;
white-space: nowrap;
transition: border-color 0.12s, color 0.12s, background 0.12s;
position: relative;
}
.chat-peer-tab:hover {
border-color: var(--t-accent);
color: var(--t-accent);
}
.chat-peer-tab.active {
border-color: var(--t-accent);
background: var(--t-accent);
color: var(--t-bg);
}
.chat-peer-tab .chat-peer-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--t-accent);
flex-shrink: 0;
}
.chat-peer-tab.active .chat-peer-dot { background: var(--t-bg); }
.chat-thread-messages {
flex: 1;
overflow-y: auto;
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.chat-bubble {
max-width: 80%;
padding: 6px 10px;
font-size: 11px;
font-family: var(--t-font-mono, monospace);
word-wrap: break-word;
white-space: pre-wrap;
line-height: 1.4;
border: 1px solid var(--t-border);
/* Pick up the user's card-radius design token so chat bubbles look
consistent with the rest of the surface (sharp / rounded / pill). */
border-radius: var(--ui-radius-card, 0px);
}
.chat-bubble.in {
align-self: flex-start;
background: var(--t-raised);
color: var(--t-bright);
border-top-left-radius: 0;
}
.chat-bubble.out {
align-self: flex-end;
background: var(--t-accent);
color: var(--t-bg);
border-color: var(--t-accent);
border-top-right-radius: 0;
}
.chat-bubble-time {
font-size: 8px;
color: var(--t-dim);
margin-top: 2px;
font-family: var(--t-font-pixel);
display: flex;
align-items: center;
gap: 4px;
}
.chat-bubble.out + .chat-bubble-time { align-self: flex-end; }
.chat-bubble.in + .chat-bubble-time { align-self: flex-start; }
/* Read receipts: clock = sent (relay not yet acked), single check =
delivered (recipient polled), double check = seen (recipient opened
the thread). Single/double share the same icon size as the timestamp. */
.chat-receipt {
font-size: 11px;
line-height: 1;
}
.chat-receipt.pending { color: var(--t-dim); }
.chat-receipt.delivered { color: var(--t-muted); }
.chat-receipt.seen { color: var(--t-accent); }
#chat-thread-form {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 10px;
border-top: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
#chat-thread-input {
flex: 1;
background: var(--t-surface);
border: 1px solid var(--t-border);
color: var(--t-bright);
padding: 6px 10px;
font-size: 11px;
font-family: var(--t-font-mono, monospace);
outline: none;
}
#chat-thread-input:focus {
border-color: var(--t-accent);
}
#chat-thread-send {
background: none;
border: none;
cursor: pointer;
padding: 4px 6px;
}
#chat-thread-send:disabled {
opacity: 0.3;
cursor: not-allowed;
}
/* ── Pick recipient view ── */
#chat-pick-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
.chat-pick-list {
flex: 1;
overflow-y: auto;
}
.chat-pick-row {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-bottom: 1px solid var(--t-border);
cursor: pointer;
font-family: var(--t-font-pixel);
font-size: 10px;
color: var(--t-bright);
transition: background 0.1s;
}
.chat-pick-row:hover {
background: var(--t-raised);
color: var(--t-accent);
}
.chat-pick-row.selected {
background: var(--t-raised);
color: var(--t-accent);
}
.chat-pick-row .chat-pick-check {
margin-left: auto;
color: var(--t-accent);
font-size: 12px;
}
.chat-pick-row:not(.selected) .chat-pick-check { visibility: hidden; }
/* Picker mode switch (1:1 vs. group). */
.chat-pick-mode {
display: flex;
gap: 4px;
padding: 6px 10px;
border-bottom: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
.chat-pick-mode-btn {
flex: 1;
padding: 3px 8px;
border: 1px solid var(--t-border);
background: transparent;
color: var(--t-muted);
font-family: var(--t-font-pixel);
font-size: 10px;
cursor: pointer;
transition: border-color 0.12s, color 0.12s, background 0.12s;
}
.chat-pick-mode-btn:hover { color: var(--t-accent); border-color: var(--t-accent); }
.chat-pick-mode-btn.active {
border-color: var(--t-accent);
background: var(--t-accent);
color: var(--t-bg);
}
.chat-group-name {
margin: 8px 10px 4px;
padding: 5px 8px;
background: var(--t-surface);
border: 1px solid var(--t-border);
color: var(--t-bright);
font-size: 11px;
font-family: var(--t-font-mono, monospace);
outline: none;
}
.chat-group-name:focus { border-color: var(--t-accent); }
.chat-group-name.hidden { display: none; }
.chat-group-actions {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-top: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
.chat-group-actions.hidden { display: none; }
/* Room-info view: member list + owner actions. */
#chat-room-info-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-bottom: 1px solid var(--t-border);
background: var(--t-bg);
flex-shrink: 0;
}
.chat-room-info-body {
flex: 1;
overflow-y: auto;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 10px;
}
.chat-room-info-body .section-label {
font-family: var(--t-font-pixel);
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--t-dim);
margin-top: 4px;
}
.chat-room-member-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
color: var(--t-bright);
padding: 3px 0;
}
.chat-room-member-row .owner-badge {
font-family: var(--t-font-pixel);
font-size: 8px;
color: var(--t-accent);
background: var(--t-raised);
padding: 1px 5px;
border: 1px solid var(--t-accent);
margin-left: 4px;
}
.chat-room-info-body .btn {
padding: 4px 10px;
font-size: 10px;
font-family: var(--t-font-pixel);
border: 1px solid var(--t-border);
background: transparent;
color: var(--t-muted);
cursor: pointer;
transition: all 0.12s;
}
.chat-room-info-body .btn:hover { border-color: var(--t-accent); color: var(--t-accent); }
.chat-room-info-body .btn.danger:hover { border-color: var(--t-danger); color: var(--t-danger); }
/* Author name above group-thread bubbles. */
.chat-bubble-author {
font-family: var(--t-font-pixel);
font-size: 8px;
color: var(--t-dim);
margin-top: 4px;
padding-left: 2px;
}
/* Peer-tab variant for rooms — group icon instead of initial. */
.chat-peer-tab .chat-peer-tab-icon {
font-size: 10px;
flex-shrink: 0;
}
/* Disposable / info icons in flyout header. */
#chat-thread-disposable.active {
color: var(--t-accent);
}
/* ── Footer button ── */
#chat-footer-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
font-size: 12px;
border: 1px solid var(--t-border);
border-radius: var(--ui-radius-button);
color: var(--t-muted);
background: transparent;
cursor: pointer;
transition: all 0.15s;
position: relative;
}
#chat-footer-btn:hover {
border-color: var(--t-accent);
color: var(--t-accent);
}
#chat-footer-btn .chat-footer-label {
font-family: var(--t-font-pixel);
font-size: 10px;
}
#chat-footer-btn.has-unread {
border-color: var(--t-accent);
color: var(--t-accent);
}
#chat-footer-btn .chat-unread-dot {
position: absolute;
top: -3px;
right: -3px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--t-accent);
display: none;
}
#chat-footer-btn.has-unread .chat-unread-dot {
display: block;
}
@keyframes chatBlink {
0%, 100% {
border-color: var(--t-accent);
color: var(--t-accent);
box-shadow: 0 0 0 0 var(--t-accent);
}
50% {
border-color: var(--t-accent);
color: var(--t-bright);
box-shadow: 0 0 8px 1px var(--t-accent);
}
}
#chat-footer-btn.blinking {
animation: chatBlink 1.2s ease-in-out infinite;
}
/* Ignore the global anim-speed override — the unread blink has a
deliberate cadence that becomes an unreadable strobe when sped up. */
body.anim-fast #chat-footer-btn.blinking,
body.anim-slow #chat-footer-btn.blinking {
animation: chatBlink 1.2s ease-in-out infinite !important;
animation-duration: 1.2s !important;
}