Files
Haven/public/css/style-sidebar.css
T

1596 lines
43 KiB
CSS

/* Haven core styles, part 4 of 16: the left sidebar, channel list, pop-out panels, main content and channel menu. Loaded in order by style.css; the order matters. */
/* ── Left Sidebar ──────────────────────────────────────── */
.sidebar {
width: var(--sidebar-width);
min-width: 12.5rem;
max-width: 25rem;
background: var(--bg-secondary);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
}
/* Resize handle (right edge of left sidebar) */
.sidebar-resize-handle {
position: absolute;
top: 0;
right: -3px;
width: 0.375rem;
height: 100%;
cursor: col-resize;
z-index: 10;
background: transparent;
transition: background 0.15s;
}
.sidebar[data-panel-pos="right"] .sidebar-resize-handle {
right: auto;
left: -3px;
}
.sidebar-resize-handle:hover,
.sidebar-resize-handle.dragging {
background: var(--accent);
opacity: 0.5;
}
.sidebar-header { padding: 1rem; border-bottom: 1px solid var(--border); }
.brand { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
.logo-sm { font-size: 1.5rem; color: var(--accent); }
.brand-icon { width: 1.75rem; height: 1.75rem; border-radius: 0.375rem; object-fit: cover; flex-shrink: 0; }
.brand-text {
font-size: 1.125rem;
font-weight: 700;
letter-spacing: 4px;
color: var(--text-primary);
font-family: var(--font-heading), var(--font-emoji);
}
.user-bar {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
background: var(--bg-primary);
border-radius: var(--radius-sm);
}
.user-names {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
min-width: 0;
}
.current-user {
font-size: 0.8125rem;
font-weight: 600;
color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.login-name {
font-size: 0.625rem;
color: var(--text-muted);
opacity: 0.7;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon-btn {
background: transparent;
border: none;
color: var(--text-muted);
font-size: 1rem;
padding: 0.25rem;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition);
display: flex;
align-items: center;
justify-content: center;
}
.icon-btn:hover { color: var(--text-primary); background: var(--bg-hover); }
.icon-btn.active { color: var(--accent); background: rgba(var(--accent-rgb, 124,92,252), 0.15); }
.icon-btn.small { font-size: 0.8125rem; }
.icon-btn.danger:hover { color: var(--danger); }
.sidebar-section { padding: 0.75rem 1rem; border-bottom: 1px solid var(--border); }
.sidebar-section.channel-section {
flex: 1;
min-height: 0;
overflow: hidden;
display: flex;
flex-direction: column;
padding-bottom: 0;
}
.section-label {
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text-muted);
margin-bottom: 0.375rem;
}
.input-row { display: flex; gap: 0.375rem; }
.input-row input {
flex: 1;
padding: 0.4375rem 0.625rem;
background: var(--bg-input);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-size: 0.8125rem;
min-width: 0;
}
.input-row input:focus { border-color: var(--accent); }
.btn-sm {
padding: 0.4375rem 0.75rem;
background: var(--bg-tertiary);
color: var(--text-secondary);
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 600;
transition: all var(--transition);
white-space: nowrap;
}
/* Rows of settings action buttons. A wrapping flexbox left these ragged --
each button sized to its own label, so they formed uneven rows of differing
widths. A grid gives every button the same width and keeps the columns
lined up; auto-fit drops to a single column when the panel is narrow. */
.settings-btn-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); /* 160px */
gap: 0.375rem; /* 6px */
margin-top: 0.5rem; /* 8px */
}
.settings-btn-grid > button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem; /* 6px */
width: 100%;
/* .btn-sm is nowrap, so a long label would otherwise spill past its cell. */
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.btn-sm:hover { background: var(--bg-hover); color: var(--text-primary); }
.btn-sm:disabled { opacity: 0.5; cursor: not-allowed; }
.btn-sm:disabled:hover { background: var(--bg-tertiary); color: var(--text-secondary); }
.btn-sm.btn-accent { background: var(--accent); color: var(--accent-text); font-size: 1rem; padding: 0.4375rem 0.625rem; line-height: 1; }
.btn-sm.btn-accent:hover { background: var(--accent-hover); }
.btn-sm.btn-danger { background: var(--danger, #e74c3c); color: var(--danger-text); font-size: 1rem; padding: 0.4375rem 0.625rem; line-height: 1; }
.btn-sm.btn-danger:hover { filter: brightness(1.15); }
/* ── Channel List ──────────────────────────────────────── */
.channel-list { flex: 1; overflow-y: auto; padding: 0.25rem 0; }
.channel-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem; /* 8px 16px */
cursor: pointer;
transition: background var(--transition);
position: relative;
}
.channel-item:hover { background: var(--bg-hover); }
.channel-item.active { background: var(--bg-active); }
.temp-channel-create-btn:hover { opacity: 0.8 !important; }
.channel-item.active::before {
content: '';
position: absolute;
left: 0; top: 4px; bottom: 4px;
width: 0.1875rem;
background: var(--accent);
border-radius: 0 0.125rem 0.125rem 0;
}
.channel-hash { color: var(--text-muted); font-weight: 700; font-size: 1rem; flex-shrink: 0; }
.channel-name {
font-size: 0.875rem;
color: var(--text-secondary);
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.channel-item.active .channel-name,
.channel-item:hover .channel-name { color: var(--text-primary); }
.channel-badge {
background: var(--danger);
color: var(--danger-text);
font-size: 0.625rem;
font-weight: 700;
min-width: 1.125rem;
height: 1.125rem;
border-radius: 0.5625rem;
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.3125rem;
}
/* Thread @mention bell badge in sidebar: distinct amber/yellow */
.channel-badge.thread-mention-badge {
background: #f5a623;
color: #1a1a1a;
font-size: 0.625rem;
margin-left: 0.25rem;
padding: 0 0.375rem;
}
/* Nested-unread dot: small marker on category labels and parent
channels to hint that one of their (uncollapsed) children has
unread messages. Visually distinct from the count bubble so the
user can tell it's a "look inside" indicator, not the unread count
itself. (#5302-ish parent-notif request) */
.channel-badge-nested-dot {
display: inline-block;
width: 0.4375rem;
height: 0.4375rem;
border-radius: 50%;
background: var(--accent, #5865f2);
opacity: 0.85;
margin-left: auto;
flex-shrink: 0;
align-self: center;
}
.channel-badge-nested-dot.has-mention {
background: var(--danger);
}
/* Header pill: "🔔 N" mentions in threads */
.thread-mentions-pill {
background: rgba(245, 166, 35, 0.18);
color: #f5a623;
border: 1px solid rgba(245, 166, 35, 0.6);
padding: 0.125rem 0.5rem;
font-weight: 700;
border-radius: 0.75rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.thread-mentions-pill:hover {
background: rgba(245, 166, 35, 0.32);
}
/* ── Pinned Messages Picture-in-Picture floating panel ─────
A draggable/resizable overlay showing the current channel's
pinned messages without blocking the message feed.
Shares .pinned-item / .pinned-list DOM from the sidebar
panel. */
.pins-pip-panel {
position: fixed;
z-index: 1010;
display: none;
flex-direction: column;
background: var(--bg-primary, #1e1e1e);
border: 1px solid var(--border, #444);
border-radius: 0.625rem;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
overflow: hidden;
resize: both;
min-width: 17.5rem;
min-height: 13.75rem;
}
.pins-pip-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
background: rgba(20, 20, 20, 0.55);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
border-bottom: 1px solid var(--border, #444);
cursor: move;
user-select: none;
flex-shrink: 0;
}
.pins-pip-icon { font-size: 0.9375rem; flex: 0 0 auto; }
.pins-pip-title {
font-weight: 600;
font-size: 0.8125rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pins-pip-spacer { flex: 1; min-width: 0; }
.pins-pip-list {
flex: 1;
overflow-y: auto;
padding: 0.25rem 0;
min-height: 0;
}
.pins-pip-panel.pins-pip-maximized { left: 50% !important; top: 50% !important; transform: translate(-50%, -50%); width: 90vw !important; height: 85vh !important; border-radius: 0.75rem; resize: none; }
.pins-pip-panel.pins-pip-maximized .pins-pip-header { cursor: default; }
.pinned-panel-actions { display: flex; align-items: center; gap: 0.125rem; }
/* ── DM Picture-in-Picture floating panel ──────────
A draggable/resizable overlay that hosts a full-fidelity DM
without leaving the user's current channel. Reuses the same
message DOM (.message / .message-compact) as the main pane,
with avatars hidden and spacing tightened for the smaller
footprint. A subtle banner image (server banner) bleeds
through behind the messages. */
.dm-pip-panel {
position: fixed;
z-index: 99999;
display: none;
flex-direction: column;
background: var(--bg-primary, #1e1e1e);
border: 1px solid var(--border, #444);
border-radius: 0.625rem;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
overflow: hidden;
resize: both;
min-width: 20rem;
min-height: 17.5rem;
}
/* Banner background: rendered behind everything else */
.dm-pip-banner {
position: absolute;
inset: 0;
background-size: cover;
background-position: center;
opacity: 0.18;
filter: saturate(1.1);
pointer-events: none;
z-index: 0;
}
.dm-pip-panel::after {
/* Soft dark gradient overlay so message text remains readable
when a banner image is set. */
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg, rgba(0,0,0,0.35) 0%, rgba(0,0,0,0.55) 100%);
pointer-events: none;
z-index: 0;
}
.dm-pip-panel.no-banner::after { background: transparent; }
.dm-pip-header,
.dm-pip-messages,
.dm-pip-reply-bar,
.dm-pip-typing,
.dm-pip-input-area { position: relative; z-index: 1; }
/* Who is typing in the pop-out's DM, in a slim line over its reply box. */
.dm-pip-typing { flex-shrink: 0; height: 1.1rem; padding: 0 0.75rem; font-size: 0.7rem; }
.dm-pip-header {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
background: rgba(20, 20, 20, 0.55);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
border-bottom: 1px solid var(--border, #444);
cursor: move;
user-select: none;
}
.dm-pip-avatar-wrap {
position: relative;
width: 1.75rem;
height: 1.75rem;
flex: 0 0 28px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
color: #fff;
}
/* The picture carries the shape; the wrap is left uncut so the status dot
can sit over the corner the same way the sidebar's does. */
.dm-pip-avatar-face {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
overflow: hidden;
background: var(--bg-secondary, #2a2a2a);
}
.dm-pip-avatar-face img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
border-radius: inherit;
}
.dm-pip-avatar-face.avatar-square { border-radius: 0.375rem; }
.dm-pip-avatar-face.avatar-rounded { border-radius: 0.5rem !important; }
.dm-pip-avatar-initial {
position: relative;
z-index: 0;
}
/* Online presence dot in the bottom-right of the PiP DM avatar, mirrors
the sidebar `.user-status-dot` styling so the colors stay consistent. */
.dm-pip-status-dot {
position: absolute;
right: -2px;
bottom: -2px;
width: 0.625rem;
height: 0.625rem;
border-radius: 50%;
background: #2ecc71; /* online (default) */
border: 2px solid var(--bg-primary, #1e1e1e);
box-sizing: content-box;
pointer-events: none;
}
.dm-pip-status-dot.away { background: #faa61a; border-radius: 0.125rem 0.125rem 50% 50%; }
.dm-pip-status-dot.offline { background: #95a5a6; }
/* The red live badge on a voice participant opens their stream (#5636). */
.voice-stream-badge.live { cursor: pointer; }
.dm-pip-status-dot.dnd { background: #e74c3c; }
.dm-pip-status-dot.invisible { background: transparent; border-color: var(--text-muted, #888); }
.dm-pip-title { font-weight: 600; font-size: 0.8125rem; }
.dm-pip-spacer { flex: 1; }
.dm-pip-messages {
flex: 1;
overflow-y: auto;
padding: 0.375rem 0.5rem 0.5rem;
font-size: 0.8125rem;
line-height: 1.4;
}
.dm-pip-loading {
opacity: 0.7;
font-style: italic;
text-align: center;
padding: 0.75rem;
}
/* ── Reuse main message DOM but compact it for PiP ── */
.dm-pip-messages .message,
.dm-pip-messages .message-compact {
padding: 0.125rem 0.375rem;
margin: 0;
font-size: 0.8125rem;
}
/* Defeat density-overrides (e.g. [data-density="compact"] .message-compact
{ padding-left: 2.75rem }) that would otherwise recess follow-up messages
to align under the avatar and misalign them with the first message in a
group inside the PiP. PiP always uses a flush left edge for both root and
follow-ups. */
.dm-pip-panel .dm-pip-messages .message,
.dm-pip-panel .dm-pip-messages .message-compact {
padding-left: 0.375rem;
padding-right: 0.375rem;
}
.dm-pip-messages .message-row {
gap: 0;
/* The default .message-row has padding: 4px 8px which would push the
full-message body 8px further right than the compact-message body,
making first-in-group messages look indented relative to follow-ups.
Zero out the horizontal padding so both align at the same left edge
(controlled by the .message / .message-compact padding-left above). */
padding-left: 0;
padding-right: 0;
/* Full-message hover toolbar / status slot live inside .message-row, while
compact-message equivalents live on .message-compact itself. In PiP the
outer .message has horizontal padding, so a positioned .message-row makes
parent-message controls land a few pixels left of child-message controls.
Leave the row unpositioned here so both anchor to the same outer box. */
position: static;
}
/* Hide per-message avatars; header already shows the partner pfp */
.dm-pip-messages .message-avatar,
.dm-pip-messages .message-avatar-img {
display: none !important;
}
.dm-pip-messages .message .message-body,
.dm-pip-messages .message-compact .message-body {
margin-left: 0;
padding-left: 0;
min-width: 0;
flex: 1;
}
.dm-pip-messages .message.message-has-status .message-body,
.dm-pip-messages .message-compact.message-has-status .message-body {
padding-right: 1.75rem;
}
.dm-pip-messages .message-inline-status {
right: 12px;
}
.dm-pip-messages .message-header {
gap: 0.375rem;
margin-bottom: 0.0625rem;
}
.dm-pip-messages .message-author { font-size: 0.78125rem; font-weight: 600; }
.dm-pip-messages .message-time { font-size: 0.65625rem; opacity: 0.7; }
.dm-pip-messages .message-content {
font-size: 0.8125rem;
word-wrap: break-word;
overflow-wrap: anywhere;
}
/* In PiP the avatar gutter doesn't exist, so position the compact
timestamp on the right side of the row instead. That way it appears
on hover without shifting any content leftward. */
.dm-pip-messages .message-compact .compact-time {
font-size: 0.625rem;
text-align: right;
left: auto;
right: 6px;
top: 2px;
width: auto;
padding-right: 0;
}
/* Slim reactions, polls, link previews to fit the smaller frame */
.dm-pip-messages .reactions-row { gap: 0.25rem; margin-top: 0.125rem; }
.dm-pip-messages .reaction-badge {
padding: 0.0625rem 0.375rem;
font-size: 0.6875rem;
}
.dm-pip-messages .reply-banner {
font-size: 0.6875rem;
padding: 0.125rem 0.375rem;
margin-bottom: 0.125rem;
}
.dm-pip-messages img,
.dm-pip-messages video,
.dm-pip-messages .link-preview {
max-width: 100%;
}
.dm-pip-messages .link-preview { font-size: 0.6875rem; }
/* Toolbar buttons stay clickable but become more compact */
.dm-pip-messages .msg-toolbar { font-size: 0.6875rem; }
.dm-pip-messages .msg-toolbar button { padding: 0.125rem 0.25rem; }
.dm-pip-reply-bar {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.625rem;
background: rgba(20, 20, 20, 0.65);
border-top: 1px solid var(--border, #444);
font-size: 0.6875rem;
color: var(--text-secondary, #aaa);
}
.dm-pip-reply-bar > span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-pip-input-area {
display: flex;
align-items: flex-end;
gap: 0.375rem;
padding: 0.875rem 0.5rem 0.5rem;
border-top: 1px solid var(--border, #444);
background: rgba(20, 20, 20, 0.55);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
.dm-pip-input {
flex: 1;
resize: none;
border-radius: 0.375rem;
border: 1px solid var(--border, #444);
background: var(--bg-primary, #1e1e1e);
color: var(--text-primary, #ddd);
padding: 0.375rem 0.5rem;
font-family: inherit;
font-size: 0.8125rem;
min-height: 2.125rem;
max-height: 12.5rem;
overflow-y: auto;
}
.dm-pip-send {
width: 2.125rem;
height: 2.125rem;
display: flex;
align-items: center;
justify-content: center;
background: var(--accent);
color: var(--accent-text);
border: none;
border-radius: var(--radius);
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s;
}
.dm-pip-send:hover { filter: brightness(1.15); }
/* Drag handle at the top of PiP input areas: drag upward to expand the textarea */
.pip-input-resizer {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 0.875rem;
cursor: ns-resize;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
}
/* The edit box's bar sits under the box, in the flow, so dragging down grows it (#5662) */
.pip-input-resizer.edit-resizer {
position: static;
height: 0.875rem;
margin: 0.1rem 0 0.15rem;
}
.pip-input-resizer::before {
content: '';
width: 2.25rem;
height: 0.1875rem;
background: var(--border, #555);
border-radius: 0.125rem;
opacity: 0.5;
transition: opacity 0.15s, background 0.15s;
}
.pip-input-resizer:hover::before {
opacity: 1;
background: var(--accent, #5865f2);
}
/* The same bar on the message edit box (#5662) */
.message.editing .message-content, .message-compact.editing .message-content { position: relative; padding-top: 0.875rem; }
/* Emoji picker trigger button shared by DM PiP and thread input areas */
.pip-input-emoji-btn {
flex-shrink: 0;
background: none;
border: none;
border-radius: 0.375rem;
font-size: 1.125rem;
line-height: 1;
padding: 0.25rem 0.3125rem;
cursor: pointer;
opacity: 0.7;
transition: opacity 0.15s, background 0.15s;
align-self: center;
color: var(--text-secondary, #ccc);
display: inline-flex;
align-items: center;
justify-content: center;
}
.pip-input-emoji-btn:hover { opacity: 1; background: var(--bg-tertiary); }
.pip-input-emoji-btn .tb-icon { display: inline-flex; align-items: center; justify-content: center; }
.pip-input-emoji-btn .tb-icon-emoji { font-size: 1.125rem; }
.pip-input-emoji-btn svg { width: 1.25rem; height: 1.25rem; display: block; }
/* Category headers read as buttons: dark while open, lit while collapsed
(#5756). The inline styles from the sidebar renderer are overridden here. */
.category-label.cat-pill {
margin: 6px 8px 2px;
padding: 4px 8px !important;
border: 1px solid var(--border);
border-radius: 0.375rem;
background: var(--bg-tertiary);
opacity: 0.85 !important;
}
.category-label.cat-pill:hover { opacity: 1 !important; border-color: var(--border-light, var(--border)); }
.category-label.cat-pill.cat-collapsed {
opacity: 1 !important;
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
background: color-mix(in srgb, var(--accent) 14%, transparent);
}
/* Tab mode: the headers wrap into rows; past about three rows the grid
scrolls instead of pushing the channels down. */
.category-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 6px 8px;
max-height: 6.75rem;
overflow-y: auto;
}
.category-tabs .category-label.cat-pill { margin: 0; flex: 0 1 auto; max-width: 100%; }
.category-tabs .category-label .cat-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A forum with topics you have not caught up on (app-forum-unread.js). */
.channel-forum-dot {
width: 0.5rem;
height: 0.5rem;
margin-left: 0.375rem;
border-radius: 50%;
background: var(--accent);
flex-shrink: 0;
}
.pip-input-gif-btn { font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.02em; }
/* ── Private channel styling (muted appearance) ─── */
.channel-item.private-channel .channel-name { opacity: 0.6; }
.channel-item.private-channel .channel-hash { opacity: 0.6; }
.channel-item.private-channel:hover .channel-name,
.channel-item.private-channel:hover .channel-hash { opacity: 1; }
.channel-item.private-channel.active .channel-name,
.channel-item.private-channel.active .channel-hash { opacity: 1; }
/* ── Role channel access panel ─── */
.role-channel-access-section {
margin-top: 1rem;
border-top: 1px solid var(--border);
padding-top: 0.75rem;
}
.role-channel-access-section h5 { margin: 0 0 0.5rem; }
.role-channel-access-list {
max-height: 16.25rem;
overflow-y: auto;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--bg-secondary);
padding: 0.25rem 0;
}
.rca-channel-row {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
font-size: 0.8125rem;
}
.rca-channel-row:hover { background: var(--bg-hover); }
.rca-channel-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--text-secondary);
}
.rca-channel-name.rca-sub { padding-left: 1rem; opacity: 0.85; }
.rca-channel-row label {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.6875rem;
color: var(--text-muted);
white-space: nowrap;
cursor: pointer;
}
.rca-reapply-btn {
margin-top: 0.5rem;
font-size: 0.75rem;
}
/* ── Main Content ──────────────────────────────────────── */
.main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
background: var(--bg-primary);
position: relative;
isolation: isolate; /* scope channel-header z-index:1 so it can't bleed above .right-sidebar (#5383) */
}
.channel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--border);
min-height: 3.25rem;
gap: 0.75rem;
position: relative;
/* Above the messages column: header dropdowns (the encryption menu, for
one) were painted under later siblings that also sit at z-index 1, such
as the DM safety notice (#5639). */
z-index: 2;
}
.channel-info { display: flex; align-items: center; gap: 0.625rem; min-width: 0; }
/* ── Header actions box (code/settings | search/pin) ── */
.header-actions-box {
display: flex;
align-items: center;
gap: 0.3125rem;
padding: 0.1875rem 0.5rem;
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 0.5rem;
background: var(--bg-tertiary, rgba(255,255,255,0.03));
flex-shrink: 0;
}
.header-actions-box .icon-btn {
opacity: 0.7;
transition: opacity 0.15s;
}
.header-actions-box .icon-btn:hover {
opacity: 1;
}
.header-actions-divider {
width: 0.0625rem;
height: 1.125rem;
background: var(--border, rgba(255,255,255,0.12));
flex-shrink: 0;
margin: 0 0.125rem;
}
.header-left-actions {
display: flex;
align-items: center;
gap: 0.3125rem;
flex-shrink: 0;
}
/* ── Update Available Banner ── */
.update-banner {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
color: var(--accent-text);
font-size: 0.75rem;
font-weight: 600;
border-radius: 1.25rem;
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
cursor: pointer;
animation: updateFadeIn 0.4s ease;
transition: transform 0.15s, box-shadow 0.15s;
}
.update-banner:hover {
transform: scale(1.05);
box-shadow: 0 2px 12px rgba(var(--accent-rgb, 88, 101, 242), 0.4);
color: var(--accent-text);
}
/* Low disk warning, admins only (#5505). Amber rather than red: uploads are
refused but the server is still working, so this is a "go clear some space"
nudge and not an outage. */
/* Active session rows. Deliberately plain: this is a list you scan for
something you do not recognise, not something you interact with. */
.sessions-list { margin: 0.5rem 0; }
.session-item {
display: flex;
align-items: baseline;
gap: 0.5rem;
padding: 0.375rem 0.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 0.375rem;
font-size: 0.8125rem;
}
.session-item.is-current { border-color: var(--accent); }
.session-device { font-weight: 600; color: var(--text-primary); }
.session-meta { color: var(--text-muted); font-size: 0.75rem; margin-left: auto; text-align: right; }
.session-current-tag {
font-size: 0.6875rem;
font-weight: 600;
padding: 0.0625rem 0.375rem;
border-radius: 1rem;
background: var(--accent);
color: var(--accent-text);
}
/* Member search in the right sidebar. Sits above the roster and filters it as
you type; the clear button only appears once there is something to clear. */
.user-search-wrap {
position: relative;
padding: 0 0.5rem 0.5rem;
}
.user-search-input {
width: 100%;
box-sizing: border-box;
padding: 0.375rem 1.75rem 0.375rem 0.5rem;
font-size: 0.8125rem;
font-family: inherit;
color: var(--text-primary);
background: var(--bg-tertiary, var(--bg-secondary));
border: 1px solid var(--border);
border-radius: var(--radius);
outline: none;
}
.user-search-input:focus {
border-color: var(--accent);
}
.user-search-input::placeholder { color: var(--text-muted); }
/* The native search clear button is inconsistent across browsers, so it is
hidden and replaced with one that matches the theme. */
.user-search-input::-webkit-search-cancel-button { display: none; }
.user-search-clear {
position: absolute;
right: 0.875rem;
top: 50%;
transform: translateY(-60%);
padding: 0 0.25rem;
line-height: 1;
font-size: 1rem;
color: var(--text-muted);
background: none;
border: none;
cursor: pointer;
}
.user-search-clear:hover { color: var(--text-primary); }
.disk-low-banner {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #b45309, #d97706);
color: #fff;
font-size: 0.75rem;
font-weight: 600;
border-radius: 1.25rem;
white-space: nowrap;
flex-shrink: 0;
cursor: help;
animation: updateFadeIn 0.4s ease;
}
.disk-low-dot {
width: 0.5rem;
height: 0.5rem;
background: #fff;
border-radius: 50%;
animation: updatePulse 1.5s ease-in-out infinite;
}
.update-pulse {
width: 0.5rem;
height: 0.5rem;
background: #fff;
border-radius: 50%;
animation: updatePulse 1.5s ease-in-out infinite;
}
@keyframes updatePulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(0.8); }
}
@keyframes updateFadeIn {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Desktop App Banner ── */
.desktop-app-banner {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #1abc9c, #16a085);
color: #fff;
font-size: 0.75rem;
font-weight: 600;
border-radius: 1.25rem;
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
cursor: pointer;
animation: updateFadeIn 0.4s ease;
transition: transform 0.15s, box-shadow 0.15s;
position: relative;
}
.desktop-app-banner:hover {
transform: scale(1.05);
box-shadow: 0 2px 12px rgba(26, 188, 156, 0.4);
color: #fff;
}
/* The banner is a container now; this is the part that actually navigates.
Keeps the icon and label laid out the way they were when the whole banner
was one anchor. */
.desktop-app-link {
display: inline-flex;
align-items: center;
gap: 0.375rem;
color: inherit;
text-decoration: none;
}
.desktop-app-icon {
font-size: 0.8125rem;
line-height: 1;
}
.desktop-app-text {
pointer-events: none;
}
.desktop-app-dismiss {
background: none;
border: none;
color: rgba(255,255,255,0.7);
font-size: 0.9375rem;
cursor: pointer;
padding: 0 0 0 0.25rem;
line-height: 1;
transition: color 0.15s;
}
.desktop-app-dismiss:hover {
color: #fff;
}
/* ── Android Beta Banner ── */
.android-beta-banner {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.25rem 0.75rem;
background: linear-gradient(135deg, #3ddc84, #2bb55e);
/* The fill is Android green in every theme, so this label can't follow
--accent-text. White on it measured 1.78:1 at the light end of the
gradient; this ink lands at 9.65:1 there and 6.46:1 at the dark end. */
color: #0b1f12;
font-size: 0.75rem;
font-weight: 600;
border-radius: 1.25rem;
border: none;
text-decoration: none;
white-space: nowrap;
flex-shrink: 0;
cursor: pointer;
animation: updateFadeIn 0.4s ease;
transition: transform 0.15s, box-shadow 0.15s;
position: relative;
}
.android-beta-banner:hover {
transform: scale(1.05);
box-shadow: 0 2px 12px rgba(61, 220, 132, 0.4);
color: #0b1f12;
}
.android-beta-icon {
font-size: 0.8125rem;
line-height: 1;
}
.android-beta-text {
pointer-events: none;
}
.android-beta-dismiss {
background: none;
border: none;
color: rgba(11,31,18,0.65);
font-size: 0.9375rem;
cursor: pointer;
padding: 0 0 0 0.25rem;
line-height: 1;
transition: color 0.15s;
}
.android-beta-dismiss:hover {
color: #0b1f12;
}
/* ── Android Beta Promo Popup ── */
.android-beta-promo {
max-width: 28.75rem;
text-align: center;
padding: 2.25rem 2rem 1.75rem;
border: 1px solid var(--border-light);
border-radius: 1rem;
animation: desktopPromoIn 0.35s ease;
}
.android-beta-promo-icon {
font-size: 3rem;
width: 5rem;
height: 5rem;
line-height: 80px;
margin: 0 auto 1rem;
background: linear-gradient(135deg, #3ddc84, #2bb55e);
border-radius: 1.125rem;
display: flex;
align-items: center;
justify-content: center;
}
.android-beta-promo-title {
font-size: 1.375rem;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 0.25rem;
text-align: center;
}
.android-beta-promo-badge {
display: inline-block;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
background: linear-gradient(135deg, #3ddc84, #2bb55e);
color: #fff;
padding: 0.125rem 0.5rem;
border-radius: 0.625rem;
margin-bottom: 0.375rem;
}
.android-beta-promo-subtitle {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0 0 1rem;
}
.android-beta-promo-features {
list-style: none;
padding: 0;
margin: 0 0 1rem;
text-align: left;
display: inline-block;
}
.android-beta-promo-features li {
font-size: 0.875rem;
color: var(--text-primary);
padding: 0.25rem 0;
display: flex;
align-items: center;
gap: 0.625rem;
}
.android-beta-promo-note {
font-size: 0.8125rem;
color: var(--text-secondary);
margin: 0 0 1rem;
}
.android-beta-form {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.625rem;
margin: 0 0 1rem;
}
.android-beta-email-input {
width: 100%;
max-width: 20rem;
padding: 0.625rem 0.875rem;
font-size: 0.875rem;
border-radius: 0.5rem;
border: 1px solid var(--border);
background: var(--bg-secondary);
color: var(--text-primary);
outline: none;
transition: border-color 0.15s;
}
.android-beta-email-input:focus {
border-color: #3ddc84;
box-shadow: 0 0 0 2px rgba(61, 220, 132, 0.2);
}
.android-beta-submit {
padding: 0.625rem 1.5rem;
font-size: 0.875rem;
font-weight: 600;
border-radius: 0.5rem;
background: linear-gradient(135deg, #3ddc84, #2bb55e);
color: #fff;
border: none;
cursor: pointer;
transition: transform 0.1s, box-shadow 0.15s;
}
.android-beta-submit:hover {
transform: scale(1.03);
box-shadow: 0 4px 16px rgba(61, 220, 132, 0.3);
}
.android-beta-promo-credit {
font-size: 0.75rem;
color: var(--text-muted);
margin: 0 0 1rem;
font-style: italic;
}
.android-beta-promo-actions {
display: flex;
justify-content: center;
gap: 0.625rem;
margin: 0 0 0.75rem;
}
.android-beta-promo-later {
padding: 0.625rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.5rem;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border);
cursor: pointer;
transition: background 0.15s;
}
.android-beta-promo-later:hover {
background: var(--bg-tertiary);
}
.android-beta-promo-dismiss-label {
font-size: 0.75rem;
color: var(--text-muted);
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
cursor: pointer;
user-select: none;
}
.android-beta-promo-dismiss-label input[type="checkbox"] {
accent-color: #3ddc84;
cursor: pointer;
}
/* ── Desktop App Promo Popup ── */
.desktop-promo {
max-width: 27.5rem;
text-align: center;
padding: 2.25rem 2rem 1.75rem;
border: 1px solid var(--border-light);
border-radius: 1rem;
animation: desktopPromoIn 0.35s ease;
}
@keyframes desktopPromoIn {
from { opacity: 0; transform: scale(0.92) translateY(12px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
.desktop-promo-icon {
font-size: 3rem;
width: 5rem;
height: 5rem;
line-height: 80px;
margin: 0 auto 1rem;
background: var(--accent);
border-radius: 1.125rem;
display: flex;
align-items: center;
justify-content: center;
}
.desktop-promo-title {
font-size: 1.375rem;
font-weight: 700;
color: var(--text-primary);
margin: 0 0 0.25rem;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
}
.desktop-promo-badge {
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.6px;
background: var(--accent);
color: var(--accent-text);
padding: 0.125rem 0.5rem;
border-radius: 0.625rem;
}
.desktop-promo-subtitle {
font-size: 0.875rem;
color: var(--text-secondary);
margin: 0 0 1.25rem;
}
.desktop-promo-features {
list-style: none;
padding: 0;
margin: 0 0 1rem;
text-align: left;
display: inline-block;
}
.desktop-promo-features li {
font-size: 0.875rem;
color: var(--text-primary);
padding: 0.25rem 0;
display: flex;
align-items: center;
gap: 0.625rem;
}
.promo-check {
color: var(--accent);
font-weight: 700;
font-size: 1rem;
flex-shrink: 0;
}
.desktop-promo-meta {
font-size: 0.75rem;
color: var(--text-muted);
margin: 0 0 1.25rem;
}
.desktop-promo-actions {
display: flex;
justify-content: center;
gap: 0.625rem;
margin: 0 0 1rem;
}
.desktop-promo-install {
padding: 0.625rem 1.5rem;
font-size: 0.875rem;
font-weight: 600;
border-radius: 0.5rem;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.375rem;
background: var(--accent);
color: var(--accent-text);
border: none;
cursor: pointer;
transition: background 0.15s, transform 0.1s;
}
.desktop-promo-install:hover {
background: var(--accent-hover);
color: var(--accent-text);
transform: scale(1.03);
}
.desktop-promo-later {
padding: 0.625rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.5rem;
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border);
cursor: pointer;
transition: background 0.15s;
}
.desktop-promo-later:hover {
background: var(--bg-tertiary);
}
.desktop-promo-dismiss-label {
font-size: 0.75rem;
color: var(--text-muted);
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
cursor: pointer;
user-select: none;
}
.desktop-promo-dismiss-label input[type="checkbox"] {
accent-color: var(--accent);
cursor: pointer;
}
/* Disabled-feature badge (streams/music not allowed): red diagonal slash overlay */
.ch-disabled-badge {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
width: 0.875rem;
height: 0.875rem;
font-size: 0.65rem;
opacity: 0.75;
overflow: hidden;
}
.ch-disabled-badge::after {
content: '';
position: absolute;
width: 0.125rem;
height: 1.25rem;
background: #e74c3c;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(-45deg);
pointer-events: none;
}
/* Channel context menu (sidebar "..." hover button) */
.channel-item { position: relative; }
/* ── Drag-and-drop channel reordering ──────────────────── */
.ch-dragging { opacity: 0.4; cursor: grabbing !important; }
.channel-item[draggable="true"],
.category-label[draggable="true"],
.sub-channel-item[draggable="true"] { cursor: grab; }
.ch-drag-indicator {
height: 0.125rem;
background: var(--accent);
border-radius: 0.125rem;
margin: 0.0625rem 0.5rem;
pointer-events: none;
flex-shrink: 0;
}
.channel-more-btn {
background: none;
border: none;
color: var(--text-secondary);
font-size: 1rem;
cursor: pointer;
border-radius: var(--radius-sm);
/* Hidden until the row is hovered or focused, and it takes no width while
hidden so the channel name gets the room (#5709). It stays in the layout
rather than display:none so it can still be tabbed to, and so the menu it
opens (right-click included) has a place to anchor. */
opacity: 0;
max-width: 0;
padding: 0.125rem 0;
overflow: hidden;
pointer-events: none;
transition: opacity .15s;
line-height: 1;
flex-shrink: 0;
margin-left: auto;
}
.channel-item:hover .channel-more-btn,
.channel-item:focus-within .channel-more-btn {
opacity: 1;
max-width: 2em;
padding: 0.125rem 0.3125rem;
pointer-events: auto;
}
.channel-more-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
/* A muted server's icon (app-server-mute.js). */
.server-icon.server-muted::after {
content: '🔕';
position: absolute;
left: -2px;
bottom: -2px;
font-size: 0.625rem;
line-height: 1;
pointer-events: none;
filter: drop-shadow(0 0 2px var(--bg-primary));
}
.server-icon.server-muted { opacity: 0.75; }
/* Arranging your own channel order (#5779, app-channel-personal-order.js). */
.arrange-channels-btn.active { opacity: 1 !important; color: var(--accent); }
.arrange-bar {
margin: 4px 8px 6px;
padding: 6px 8px;
border: 1px dashed var(--accent);
border-radius: var(--radius-sm);
font-size: 0.75rem;
color: var(--text-secondary);
}
.arrange-bar-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 6px; }
.channel-list.arranging .channel-item:not(.sub-channel-item),
.channel-list.arranging .category-label { cursor: grab; }
.channel-ctx-menu {
position: fixed;
/* Above the thread panel (99996, PiP 99999), where a forum post opens,
so a channel or topic menu is not drawn behind it (#5762). Below the
modal overlay (100001). */
z-index: 100000;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
min-width: 10.625rem;
padding: 0.25rem;
}
.channel-ctx-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
background: none;
border: none;
color: var(--text-primary);
font-size: 0.8125rem;
cursor: pointer;
border-radius: var(--radius-sm);
white-space: nowrap;
width: 100%;
text-align: left;
}
.channel-ctx-item:hover { background: var(--bg-hover); }
.channel-ctx-item.danger { color: var(--danger); }
.channel-ctx-item.danger:hover { background: #3a1515; }
.ctx-indicator {
margin-left: auto;
font-size: 0.85em;
padding-left: 0.625rem;
white-space: nowrap;
}
/* ── Channel Functions popout panel ── */
.channel-functions-panel {
position: fixed;
background: var(--bg-secondary);
border-radius: 0.375rem;
padding: 0.1875rem 0;
border: 1px solid var(--border);
box-shadow: 0 4px 16px rgba(0,0,0,0.35);
z-index: 10000;
min-width: 10.625rem;
}
.cfn-divider {
border-top: 1px solid var(--border);
margin: 0.1875rem 0.5rem;
}
.cfn-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.3125rem 0.625rem;
border-radius: 0.25rem;
cursor: pointer;
user-select: none;
transition: background 0.1s;
}
.cfn-row:hover { background: var(--bg-hover); }
.cfn-row.cfn-disabled {
opacity: 0.38;
cursor: not-allowed;
pointer-events: none;
}
.cfn-row.cfn-disabled .cfn-label {
text-decoration: line-through;
text-decoration-color: rgba(255,255,255,0.45);
}
.cfn-label {
font-size: 0.75rem;
color: var(--text-secondary);
}
.cfn-badge {
font-size: 0.625rem;
font-weight: 700;
padding: 0.125rem 0.4375rem;
border-radius: 0.1875rem;
min-width: 1.875rem;
text-align: center;
letter-spacing: 0.3px;
}
.cfn-badge.cfn-on {
background: rgba(87, 242, 135, 0.15);
color: #57f287;
}
.cfn-badge.cfn-off {
background: rgba(255,255,255,0.06);
color: var(--text-muted);
}
.cfn-input {
width: 3.375rem;
background: var(--bg-primary);
border: 1px solid var(--accent, var(--border));
border-radius: 0.1875rem;
color: var(--text-primary);
font-size: 0.6875rem;
padding: 0.125rem 0.3125rem;
text-align: right;
outline: none;
}
select.cfn-select.cfn-input {
width: auto;
max-width: 7.5rem;
text-align: left;
}
/* Arrow indicator on the Channel Functions button */
.channel-ctx-item [class="cfn-arrow"] {
margin-left: auto;
font-size: 0.5625rem;
color: var(--text-muted);
}
.channel-ctx-sep {
border: none;
border-top: 1px solid var(--border);
margin: 0.1875rem 0.375rem;
}
.channel-info h3 {
font-size: 1rem;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: var(--font-heading), var(--font-emoji);
}
.channel-code-tag {
font-size: 0.6875rem;
font-family: var(--font-mono);
color: var(--text-muted);
background: var(--bg-tertiary);
padding: 0.125rem 0.5rem; /* 2px 8px, scales with the font like the tag text */
border-radius: 10px;
letter-spacing: 0.5px;
}
.channel-code-tag:empty { display: none; }
/* ── Welcome Screen ────────────────────────────────────── */
.welcome-screen { flex: 1; display: flex; align-items: center; justify-content: center; }
.welcome-content { text-align: center; color: var(--text-secondary); }
.welcome-icon { font-size: 4.5rem; color: var(--accent); opacity: 0.5; margin-bottom: 1rem; }
.welcome-content h2 {
font-size: 1.5rem;
color: var(--text-primary);
margin-bottom: 0.5rem;
font-family: var(--font-heading), var(--font-emoji);
}
.welcome-content p { font-size: 0.875rem; line-height: 1.6; }