v3: chatbox improvements (typing indicators, answer to previous messages, backlog days separators, scroll to last message position, visual tweaks)
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 41s

This commit is contained in:
2026-06-30 12:27:03 +02:00
parent 124866ad4f
commit d2d086831e
17 changed files with 857 additions and 86 deletions

View File

@@ -3292,12 +3292,14 @@ body.has-player .chat-fab {
}
/* ── Live chat ── */
/* The chat fills the (now taller) modal so it makes better use of the viewport
on desktop — see the .modal-card:has(.chat) height override below. */
.chat {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
height: min(60vh, 480px);
height: 100%;
gap: 0.75rem;
}
@@ -3306,12 +3308,13 @@ body.has-player .chat-fab {
min-height: 0;
overflow-y: auto;
/* Reserve a gutter for the scrollbar so it never sits on top of the
messages — without this, overlay scrollbars overlap the content. */
messages — without this, overlay scrollbars overlap the content. The extra
padding keeps the text clear of the scrollbar rather than right against it. */
scrollbar-gutter: stable;
display: flex;
flex-direction: column;
gap: 0.85rem;
padding-right: 0.5rem;
padding-right: 1rem;
}
.chat-empty {
@@ -3352,6 +3355,40 @@ body.has-player .chat-fab {
}
}
/* Brief highlight when a message is jumped to via a reply reference. */
.chat-message {
border-radius: 8px;
transition: background-color 0.4s ease;
}
.chat-message--flash {
background-color: color-mix(in srgb, var(--color-accent) 18%, transparent);
}
/* Subtle day separator: a hairline rule with a centered, muted label. */
.chat-day-sep {
display: flex;
align-items: center;
gap: 0.6rem;
margin: 0.15rem 0;
color: var(--color-text-muted);
}
.chat-day-sep::before,
.chat-day-sep::after {
content: "";
flex: 1;
height: 1px;
background: var(--color-border);
}
.chat-day-sep-label {
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.chat-message-body {
flex: 1;
min-width: 0;
@@ -3497,6 +3534,177 @@ body.has-player .chat-fab {
box-sizing: border-box;
}
/* ── Reply reference (subtle, above a message's body) ── */
.chat-reply-ref {
display: flex;
align-items: baseline;
gap: 0.3rem;
max-width: 100%;
margin-bottom: 0.15rem;
padding: 0;
background: none;
border: none;
font: inherit;
font-size: 0.74rem;
color: var(--color-text-muted);
cursor: pointer;
text-align: left;
}
.chat-reply-ref:disabled {
cursor: default;
font-style: italic;
}
.chat-reply-ref:not(:disabled):hover .chat-reply-ref-author,
.chat-reply-ref:not(:disabled):hover .chat-reply-ref-text {
color: var(--color-text);
}
.chat-reply-ref-mark {
opacity: 0.7;
flex-shrink: 0;
}
.chat-reply-ref-author {
font-weight: 700;
flex-shrink: 0;
transition: color 0.15s;
}
.chat-reply-ref-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0.85;
transition: color 0.15s;
}
/* ── Reply composer bar (subtle, above the input) ── */
.chat-reply-bar {
display: flex;
align-items: center;
gap: 0.4rem;
flex-shrink: 0;
padding: 0.35rem 0.6rem;
border-left: 2px solid var(--color-accent);
border-radius: 4px;
background: color-mix(in srgb, var(--color-accent) 8%, transparent);
font-size: 0.78rem;
color: var(--color-text-muted);
}
.chat-reply-bar-mark {
opacity: 0.7;
flex-shrink: 0;
}
.chat-reply-bar-text {
display: flex;
align-items: baseline;
gap: 0.3rem;
min-width: 0;
flex: 1;
}
.chat-reply-bar-author {
font-weight: 700;
color: var(--color-text);
flex-shrink: 0;
}
.chat-reply-bar-snippet {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0.8;
}
.chat-reply-bar-cancel {
margin-left: auto;
flex-shrink: 0;
font-size: 0.85rem;
}
/* ── "Is typing" indicator (reuses presence avatars) ── */
.chat-typing {
display: flex;
align-items: center;
gap: 0.45rem;
flex-shrink: 0;
min-height: 1.25rem;
font-size: 0.76rem;
color: var(--color-text-muted);
}
.chat-typing-avatars {
display: inline-flex;
}
/* Overlap multiple typers' avatars slightly, like a stacked presence cluster. */
.chat-typing-avatar:not(:first-child) {
margin-left: -0.4rem;
}
.chat-typing-avatar {
border-radius: 50%;
box-shadow: 0 0 0 2px var(--color-surface);
}
.chat-typing-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.chat-typing-dots {
display: inline-flex;
gap: 2px;
flex-shrink: 0;
}
.chat-typing-dots span {
width: 4px;
height: 4px;
border-radius: 50%;
background: currentColor;
opacity: 0.4;
animation: chat-typing-bounce 1.2s infinite ease-in-out;
}
.chat-typing-dots span:nth-child(2) {
animation-delay: 0.15s;
}
.chat-typing-dots span:nth-child(3) {
animation-delay: 0.3s;
}
@keyframes chat-typing-bounce {
0%, 60%, 100% {
opacity: 0.3;
transform: translateY(0);
}
30% {
opacity: 0.9;
transform: translateY(-2px);
}
}
@media (prefers-reduced-motion: reduce) {
.chat-typing-dots span {
animation: none;
}
}
/* Give the chat a tall, stable panel so it uses the viewport well on desktop
instead of floating as a small box. Bounded by max-height so it never spills
off short screens; the inner message list scrolls within it. */
.modal-card:has(.chat) {
height: min(86vh, 820px);
max-height: 92vh;
}
/* The composer's popovers (mention list, emoji picker) anchor to the input,
which sits at the very bottom of the modal. The modal body must not clip them
— let them escape the body entirely so they're never cropped. The message