mirror of
https://github.com/ancsemi/Haven.git
synced 2026-10-27 17:15:24 +00:00
1323 lines
45 KiB
CSS
1323 lines
45 KiB
CSS
/* Haven core styles, part 5 of 16: the message area, avatars and the avatar editor, sounds, the message box and the right sidebar. Loaded in order by style.css; the order matters. */
|
|
/* ── Message Area ──────────────────────────────────────── */
|
|
|
|
.message-area { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
|
|
|
|
/* ── Jump to Bottom button ───────────────────────────── */
|
|
.jump-to-bottom {
|
|
position: absolute;
|
|
bottom: 120px;
|
|
right: 24px;
|
|
z-index: 50;
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
border-radius: 50%;
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
|
|
transition: background 0.15s ease, color 0.15s ease, opacity 0.2s ease, transform 0.2s ease;
|
|
opacity: 0;
|
|
transform: translateY(8px);
|
|
pointer-events: none;
|
|
}
|
|
.jump-to-bottom.visible {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
.jump-to-bottom:hover {
|
|
background: var(--accent);
|
|
color: var(--accent-text);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.messages {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
overflow-anchor: none;
|
|
padding: 1rem 1.25rem;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.125rem;
|
|
position: relative; /* for absolutely-positioned children like #msg-more-btn */
|
|
}
|
|
|
|
.message {
|
|
display: flex;
|
|
flex-direction: column;
|
|
border-radius: var(--radius-sm);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.message:hover {
|
|
background: rgba(255,255,255,0.02);
|
|
transition: background var(--transition);
|
|
box-shadow: var(--msg-glow);
|
|
}
|
|
|
|
/* ── Message gutter ──────────────────────────────────────
|
|
A group leader indents its text by (row padding + avatar + gap). A compact
|
|
follow-up has no avatar, so it must pad by that exact same total or the
|
|
lines of one conversation don't share a left edge. Those numbers used to be
|
|
spelled out independently in five places (base, two breakpoints, two
|
|
density tiers), and the spacious tier never got its compact value at all,
|
|
so every follow-up sat 14px left of the message above it. One side is now
|
|
derived from the other: overrides set these variables and both move
|
|
together, so the two can't drift apart again. */
|
|
:root {
|
|
--msg-pad-x: 0.5rem; /* 8px: .message-row horizontal padding */
|
|
--msg-pad-y: 0.25rem; /* 4px: .message-row vertical padding */
|
|
--msg-avatar: 2.25rem; /* 36px: avatar column */
|
|
--msg-gap: 0.75rem; /* 12px: avatar → body gap */
|
|
--msg-gutter: calc(var(--msg-pad-x) + var(--msg-avatar) + var(--msg-gap));
|
|
}
|
|
|
|
.message-row {
|
|
display: flex;
|
|
gap: var(--msg-gap);
|
|
padding: var(--msg-pad-y) var(--msg-pad-x);
|
|
position: relative;
|
|
}
|
|
|
|
.message-avatar {
|
|
width: var(--msg-avatar);
|
|
height: var(--msg-avatar);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
font-size: 0.875rem; /* 14px */
|
|
color: white;
|
|
flex-shrink: 0;
|
|
margin-top: 0.125rem;
|
|
}
|
|
|
|
.message-avatar-img {
|
|
width: var(--msg-avatar);
|
|
height: var(--msg-avatar);
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
flex-shrink: 0;
|
|
margin-top: 0.125rem;
|
|
}
|
|
|
|
/* pfp border overlay: the saved fit rendered on top of a real avatar. The host
|
|
is a positioning context sized to the avatar; the marker (.pfp-border) is
|
|
folded into .bce-op wrappers by _foldPfpBorder. Reuses .bce-op. */
|
|
/* align-self stops the flex row from stretching the host to full row height
|
|
(tallest on emoji-only jumbo rows); block avatar drops the inline descender,
|
|
so the host is exactly the avatar box and the overlay stays centered on it. */
|
|
.pfp-host { position: relative; display: inline-block; flex-shrink: 0; align-self: flex-start; margin-top: 0.125rem; }
|
|
.pfp-host .message-avatar, .pfp-host .message-avatar-img { margin-top: 0; }
|
|
.pfp-host .message-avatar-img { display: block; }
|
|
/* No clip net: _computeMatrix3d refuses any distort whose perspective could fling
|
|
the border past the frame, so nothing here needs cropping. */
|
|
.pfp-border { position: absolute; inset: 0; pointer-events: none; }
|
|
.pfp-border-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
|
|
|
|
/* User list avatars */
|
|
/* Discord-style avatar wrapper with overlaid status dot */
|
|
.user-avatar-wrapper {
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
}
|
|
|
|
.user-status-dot {
|
|
position: absolute;
|
|
bottom: -2px;
|
|
right: -2px;
|
|
width: 0.625rem;
|
|
height: 0.625rem;
|
|
border-radius: 50%;
|
|
background: var(--success);
|
|
border: 2px solid var(--bg-secondary);
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Away: half-circle (dome), visually distinct from full circle */
|
|
/* Official message (posted as a role): the role's colour down the side, a
|
|
light wash of it behind, and the role's name as a badge. */
|
|
.message.message-highlighted,
|
|
.message-compact.message-highlighted {
|
|
box-shadow: inset 3px 0 0 var(--hl-color, var(--accent));
|
|
background: color-mix(in srgb, var(--hl-color, var(--accent)) 9%, transparent);
|
|
}
|
|
.msg-highlight-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0 0.375rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 0.6875rem;
|
|
font-weight: 700;
|
|
line-height: 1.4;
|
|
color: var(--hl-color, var(--accent));
|
|
border: 1px solid color-mix(in srgb, var(--hl-color, var(--accent)) 55%, transparent);
|
|
background: color-mix(in srgb, var(--hl-color, var(--accent)) 14%, transparent);
|
|
white-space: nowrap;
|
|
}
|
|
.btn-highlight.active { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
/* The reader's choice of look (app-message-highlights.js). Coloured text:
|
|
the text and a lighter wash in the role's colour, a thinner bar, no
|
|
badge, and a run of official messages from one person joined into one
|
|
block. The text colour is mixed with the theme's own, more of it on light
|
|
themes (--hl-text-mix), so a pale role colour stays readable. The text
|
|
colour is !important because many themes set message text that way. Off:
|
|
no mark at all. */
|
|
:root[data-official-style] .msg-highlight-badge { display: none; }
|
|
:root[data-official-style="off"] .message.message-highlighted,
|
|
:root[data-official-style="off"] .message-compact.message-highlighted {
|
|
box-shadow: none;
|
|
background: none;
|
|
}
|
|
:root[data-official-style="text"] .message.message-highlighted,
|
|
:root[data-official-style="text"] .message-compact.message-highlighted {
|
|
box-shadow: inset 2px 0 0 color-mix(in srgb, var(--hl-color, var(--accent)) 75%, transparent);
|
|
background: color-mix(in srgb, var(--hl-color, var(--accent)) 7%, transparent);
|
|
}
|
|
:root[data-official-style="text"] .message-highlighted .message-content {
|
|
color: color-mix(in srgb, var(--hl-color, var(--accent)) var(--hl-text-mix, 60%), var(--text-primary)) !important;
|
|
}
|
|
:root[data-official-style="text"] .message-highlighted:has(+ .message-compact.message-highlighted) {
|
|
border-bottom-left-radius: 0;
|
|
border-bottom-right-radius: 0;
|
|
}
|
|
:root[data-official-style="text"] .message-highlighted + .message-compact.message-highlighted {
|
|
margin-top: -0.125rem;
|
|
padding-top: 0.1875rem;
|
|
border-top-left-radius: 0;
|
|
border-top-right-radius: 0;
|
|
}
|
|
|
|
.user-status-dot.away {
|
|
background: #faa61a;
|
|
border-radius: 0.125rem 0.125rem 50% 50%;
|
|
}
|
|
|
|
/* DND: square, immediately distinguishable */
|
|
.user-status-dot.dnd {
|
|
background: var(--danger);
|
|
border-radius: 0.125rem;
|
|
}
|
|
|
|
/* Invisible / Offline: hollow circle outline */
|
|
.user-status-dot.invisible {
|
|
background: transparent;
|
|
border: 2px solid var(--text-muted);
|
|
}
|
|
|
|
.user-item.offline .user-status-dot {
|
|
background: transparent;
|
|
border: 2px solid var(--text-muted);
|
|
}
|
|
|
|
.user-item-avatar {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
font-size: 0.625rem;
|
|
color: white;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.user-item-avatar-img {
|
|
width: 1.5rem;
|
|
height: 1.5rem;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Avatar settings */
|
|
.avatar-settings { padding: 0.25rem 0; position: relative; z-index: 1; }
|
|
.settings-section-subtitle { font-size: 0.8125rem; font-weight: 600; color: var(--text-secondary); margin-bottom: 0.625rem; margin-top: 1rem; padding-bottom: 0.375rem; border-bottom: 1px solid var(--border); }
|
|
|
|
/* Avatar Upload Row (Anmire-style) */
|
|
.avatar-upload-row { display: flex; align-items: center; gap: 0.5rem; }
|
|
.avatar-upload-preview {
|
|
width: 3rem;
|
|
height: 3rem;
|
|
border-radius: 50%;
|
|
overflow: hidden;
|
|
flex-shrink: 0;
|
|
border: 2px solid var(--border);
|
|
background: var(--bg-tertiary);
|
|
}
|
|
.avatar-upload-preview img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
border-radius: 50%;
|
|
}
|
|
|
|
/* Border edit modal: shaped avatar as the base layer, border image on top.
|
|
Both layers fill the same square so the shape and frame stay aligned. The
|
|
tool group (modes + stage) sits left, the edit history list sits right. */
|
|
/* Wider than a default modal so the stage and history list sit comfortably. */
|
|
#border-crop-modal .modal { width: min(92%, 36rem); }
|
|
.border-crop-body { display: flex; gap: 0.75rem; align-items: stretch; }
|
|
.border-crop-main { flex: 1; min-width: 0; }
|
|
.border-crop-stage { display: flex; align-items: center; justify-content: center; padding: 1.5rem 0; }
|
|
.border-crop-preview { position: relative; width: 14.4rem; height: 14.4rem; }
|
|
/* Purely visual: all interaction goes through .border-crop-box. Without this, a
|
|
scaled/moved op wrapper's hit area overflows the stage and swallows clicks on
|
|
the tool tabs above it. */
|
|
.border-crop-layers { position: absolute; inset: 0; pointer-events: none; }
|
|
.border-crop-avatar {
|
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
object-fit: cover;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-weight: 700; font-size: 3rem; color: #fff;
|
|
}
|
|
.border-crop-border {
|
|
position: absolute; inset: 0; width: 100%; height: 100%;
|
|
object-fit: contain; pointer-events: none;
|
|
}
|
|
/* One folded op = one nested wrapper. The transform/clip is set inline in JS;
|
|
every wrapper fills its parent so ops compose cleanly from image outward. */
|
|
.bce-op { position: absolute; inset: 0; }
|
|
/* Avatar mode: the unedited image, faded under the crop so the cut area shows. */
|
|
.bce-ghost { opacity: 0.3; }
|
|
|
|
/* Edit history (the op log), newest at the bottom, each row undoes itself + later. */
|
|
.border-crop-side { width: 8.5rem; display: flex; flex-direction: column; }
|
|
.border-crop-side-title { margin: 0 0 0.4rem; font-size: 0.8125rem; color: var(--text-secondary); }
|
|
.border-crop-history { flex: 1; overflow-y: auto; max-height: 15rem; display: flex; flex-direction: column; gap: 0.25rem; }
|
|
.bce-hist-empty { color: var(--text-muted); font-size: 0.8125rem; padding: 0.35rem 0; }
|
|
.bce-hist-row {
|
|
display: flex; align-items: center; justify-content: space-between; gap: 0.4rem;
|
|
padding: 0.3rem 0.45rem; border: 1px solid var(--border); border-radius: 0.4rem;
|
|
background: var(--bg-tertiary, #2a2a2e); font-size: 0.8125rem;
|
|
}
|
|
.bce-hist-label { color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.bce-hist-undo {
|
|
flex: none; border: none; background: none; cursor: pointer; padding: 0 0.15rem;
|
|
color: var(--text-muted); font-size: 1rem; line-height: 1;
|
|
}
|
|
.bce-hist-undo:hover { color: var(--accent, #7aa2ff); }
|
|
|
|
/* Segmented mode selector: pick one tool. */
|
|
.border-crop-modes { display: flex; border: 1px solid var(--border); border-radius: 0.5rem; overflow: hidden; margin-bottom: 0.75rem; }
|
|
.border-crop-mode-btn {
|
|
flex: 1; padding: 0.4rem 0.5rem; border: none; border-right: 1px solid var(--border);
|
|
background: var(--bg-tertiary, #2a2a2e); color: var(--text-secondary); cursor: pointer;
|
|
font-size: 0.8125rem; font-weight: 600;
|
|
}
|
|
.border-crop-mode-btn:last-child { border-right: none; }
|
|
.border-crop-mode-btn.active { background: var(--accent, #7aa2ff); color: var(--accent-text); }
|
|
|
|
/* Shown only for the Crop tool (toggled in _renderBorderEditor). Auto-positions
|
|
the crop handles around the border's opaque pixels; user edits/keeps from there. */
|
|
.border-crop-autocrop-btn {
|
|
display: none; width: 100%; padding: 0.4rem 0.5rem; margin-bottom: 0.75rem;
|
|
border: 1px solid var(--border); border-radius: 0.5rem;
|
|
background: var(--bg-tertiary, #2a2a2e); color: var(--text-secondary); cursor: pointer;
|
|
font-size: 0.8125rem; font-weight: 600;
|
|
}
|
|
.border-crop-autocrop-btn:hover { color: var(--text-primary); border-color: var(--accent, #7aa2ff); }
|
|
.border-crop-autocrop-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
.border-crop-autocrop-btn:disabled:hover { color: var(--text-secondary); border-color: var(--border); }
|
|
/* Lock sides toggle (avatar editor only); lit while on. */
|
|
.border-crop-lock-btn.active { background: var(--accent, #7aa2ff); color: var(--accent-text); border-color: var(--accent, #7aa2ff); }
|
|
|
|
/* Shown only for the Rotate tool (toggled in _renderBorderEditor). Quarter-turn
|
|
nudges that wrap through the same 0..360 logic as the manual rotate handle. */
|
|
.border-crop-rotate-actions { display: none; gap: 0.5rem; margin-bottom: 0.75rem; }
|
|
.border-crop-rotate-actions .btn-sm { flex: 1; text-align: center; }
|
|
|
|
/* Shown only for the Opacity tool (toggled in _renderBorderEditor). */
|
|
.border-crop-resize-actions { display: none; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
|
.border-crop-resize-actions #border-resize-anchor { flex: 1; }
|
|
.border-resize-anchor-label { font-size: 0.8125rem; color: var(--text-secondary); white-space: nowrap; }
|
|
.border-crop-opacity-actions { display: none; align-items: center; gap: 0.5rem; margin-bottom: 0.75rem; }
|
|
.border-crop-opacity-actions #border-opacity-slider { flex: 1; }
|
|
.border-opacity-value { min-width: 3ch; text-align: right; font-size: 0.8125rem; font-weight: 600; color: var(--text-secondary); }
|
|
|
|
/* Drag handles overlaid on the border. All are positioned in JS (so they follow
|
|
the border) and hidden by default; only the active mode's handles show. */
|
|
.border-crop-box { position: absolute; inset: 0; touch-action: none; }
|
|
/* Move is the only tool that drags the image body, so it owns the move cursor. */
|
|
.border-crop-preview.mode-move .border-crop-box { cursor: move; }
|
|
.border-crop-handle {
|
|
position: absolute; width: 0.875rem; height: 0.875rem;
|
|
border-radius: 50%; background: var(--accent, #7aa2ff); border: 2px solid #fff;
|
|
box-sizing: border-box; touch-action: none; transform: translate(-50%, -50%);
|
|
display: none; align-items: center; justify-content: center;
|
|
}
|
|
.crop-edge { cursor: crosshair; }
|
|
.corner-handle { background: var(--bg-secondary, #26262b); cursor: grab; }
|
|
.corner-handle:active { cursor: grabbing; }
|
|
.scale-handle, .rotate-handle { width: 1.25rem; height: 1.25rem; font-size: 0.8rem; color: #fff; line-height: 1; }
|
|
.scale-handle { cursor: nwse-resize; }
|
|
.rotate-handle { background: var(--bg-secondary, #26262b); cursor: grab; }
|
|
.rotate-handle:active { cursor: grabbing; }
|
|
.border-crop-preview.mode-crop .crop-edge,
|
|
.border-crop-preview.mode-distort .corner-handle,
|
|
.border-crop-preview.mode-resize .scale-handle,
|
|
.border-crop-preview.mode-rotate .rotate-handle { display: flex; }
|
|
|
|
/* Avatar Shape Picker */
|
|
.avatar-shape-picker { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
.avatar-shape-btn {
|
|
width: 2.375rem; height: 2.375rem;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: var(--bg-tertiary, #2a2a2e); border: 2px solid var(--border);
|
|
border-radius: 0.375rem; cursor: pointer;
|
|
transition: border-color 0.2s, box-shadow 0.2s;
|
|
}
|
|
.avatar-shape-btn:hover { border-color: var(--text-secondary); }
|
|
.avatar-shape-btn.active { border-color: var(--accent); box-shadow: 0 0 8px rgba(var(--accent-rgb, 88,101,242), 0.4); }
|
|
.shape-preview { width: 1.25rem; height: 1.25rem; background: var(--accent, #5865f2); }
|
|
.shape-circle { border-radius: 50%; }
|
|
.shape-rounded { border-radius: 0.25rem; }
|
|
.shape-squircle { border-radius: 28%; }
|
|
.shape-hex { clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); }
|
|
.shape-diamond { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); }
|
|
|
|
/* Avatar Shape Classes: applied to .message-avatar and .message-avatar-img */
|
|
.avatar-circle { border-radius: 50% !important; }
|
|
.avatar-rounded { border-radius: 0.375rem !important; }
|
|
.avatar-squircle { border-radius: 28% !important; }
|
|
.avatar-hex { clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%); border-radius: 0 !important; }
|
|
.avatar-diamond { clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%); border-radius: 0 !important; }
|
|
|
|
/* Size slider */
|
|
#avatar-size-slider {
|
|
width: 100%;
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
height: 0.375rem;
|
|
border-radius: 0.1875rem;
|
|
background: var(--bg-tertiary, #2a2a2e);
|
|
outline: none;
|
|
}
|
|
#avatar-size-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
width: 1rem; height: 1rem;
|
|
border-radius: 50%;
|
|
background: var(--accent, #5865f2);
|
|
cursor: pointer;
|
|
border: 2px solid var(--bg-primary, #1e1e22);
|
|
}
|
|
#avatar-size-slider::-moz-range-thumb {
|
|
width: 1rem; height: 1rem;
|
|
border-radius: 50%;
|
|
background: var(--accent, #5865f2);
|
|
cursor: pointer;
|
|
border: 2px solid var(--bg-primary, #1e1e22);
|
|
}
|
|
|
|
/* Custom sounds admin */
|
|
.custom-sound-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.custom-sound-item:last-child { border-bottom: none; }
|
|
.custom-sound-name { flex: 1; font-size: 0.8125rem; color: var(--text-primary); }
|
|
.custom-sound-name-input {
|
|
flex: 1; font-size: 0.75rem; padding: 0.125rem 0.375rem; border-radius: 0.25rem;
|
|
border: 1px solid var(--accent); background: var(--bg-input);
|
|
color: var(--text-primary); outline: none;
|
|
}
|
|
.btn-xs {
|
|
padding: 0.125rem 0.5rem;
|
|
font-size: 0.6875rem;
|
|
border-radius: 0.25rem;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
.btn-xs:hover { background: var(--bg-tertiary); }
|
|
|
|
/* ── Sound Manager Popout ─────────────────────────────────── */
|
|
.sound-modal-tabs {
|
|
display: flex; gap: 0.25rem; margin-bottom: 0.75rem;
|
|
border-bottom: 1px solid var(--border); padding-bottom: 0.5rem;
|
|
}
|
|
.sound-tab {
|
|
padding: 0.3125rem 0.75rem; font-size: 0.75rem; border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
|
border: 1px solid var(--border); border-bottom: none; background: var(--bg-tertiary);
|
|
color: var(--text-secondary); cursor: pointer; transition: background 0.15s, color 0.15s;
|
|
}
|
|
.sound-tab:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
.sound-tab.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
|
|
.sound-tab-content { display: none; }
|
|
.sound-tab-content.active { display: block; }
|
|
|
|
/* Soundboard grid */
|
|
.soundboard-grid {
|
|
display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
gap: 0.375rem; max-height: 17.5rem; overflow-y: auto; padding: 0.25rem 0;
|
|
}
|
|
.soundboard-btn {
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
gap: 0.25rem; padding: 0.625rem 0.375rem; border-radius: var(--radius);
|
|
border: 1px solid var(--border); background: var(--bg-secondary);
|
|
color: var(--text-primary); cursor: pointer; min-height: 3.75rem;
|
|
transition: background 0.15s, border-color 0.15s, transform 0.1s;
|
|
}
|
|
.soundboard-btn:hover { background: var(--bg-hover); border-color: var(--accent); }
|
|
.soundboard-btn:active { transform: scale(0.96); }
|
|
.soundboard-btn .sb-name { font-size: 0.75rem; font-weight: 600; text-align: center; word-break: break-word; }
|
|
.soundboard-btn .sb-hotkey {
|
|
font-size: 0.5625rem; color: var(--text-muted); background: var(--bg-tertiary);
|
|
padding: 0.0625rem 0.375rem; border-radius: 0.1875rem; font-family: var(--font-mono);
|
|
}
|
|
.sb-hotkey-row {
|
|
display: inline-flex; align-items: center; gap: 0.25rem;
|
|
}
|
|
.sb-hotkey-clear {
|
|
font-size: 0.8125rem; line-height: 1; color: var(--text-muted); cursor: pointer;
|
|
padding: 0 0.125rem; border-radius: 0.1875rem; transition: color 0.15s, background 0.15s;
|
|
}
|
|
.sb-hotkey-clear:hover { color: var(--danger); background: rgba(240,71,71,0.15); }
|
|
/* Row layouts (list mode, sidebar) get a finger-sized clear target. */
|
|
.soundboard-grid.list-mode .sb-hotkey-clear,
|
|
#sb-sidebar-grid .sb-hotkey-clear { font-size: 1rem; padding: 0 0.375rem; }
|
|
#sb-sidebar-grid .soundboard-btn { justify-content: space-between; }
|
|
#sb-sidebar-grid .sb-hotkey-row, #sb-sidebar-grid .sb-hotkey-set { flex-shrink: 0; margin-left: 0.5rem; }
|
|
.sb-hotkey-set {
|
|
font-size: 0.5625rem; color: var(--accent); cursor: pointer; opacity: 0.7;
|
|
transition: opacity 0.15s;
|
|
}
|
|
.sb-hotkey-set:hover { opacity: 1; text-decoration: underline; }
|
|
.sound-search-row { display: flex; gap: 0.5rem; margin-bottom: 0.5rem; }
|
|
.sound-assign-list { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
.sound-assign-select { min-width: 8.75rem; }
|
|
|
|
/* Sidebar layout mode for soundboard (used by the sidebar panel grid only) */
|
|
.soundboard-grid.sidebar-mode {
|
|
display: flex !important;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
max-height: 18.75rem;
|
|
grid-template-columns: auto;
|
|
}
|
|
.soundboard-grid.sidebar-mode .soundboard-btn {
|
|
min-height: 2rem;
|
|
justify-content: space-between;
|
|
flex-direction: row;
|
|
padding: 0.375rem 0.5rem;
|
|
}
|
|
.soundboard-grid.sidebar-mode .soundboard-btn .sb-name {
|
|
text-align: left;
|
|
flex: 1;
|
|
font-size: 0.8125rem;
|
|
}
|
|
/* List view mode for popup/pip soundboard (separate from sidebar-mode) */
|
|
.soundboard-grid.list-mode {
|
|
display: flex !important;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
grid-template-columns: auto;
|
|
}
|
|
.soundboard-grid.list-mode .soundboard-btn {
|
|
min-height: 2rem;
|
|
justify-content: space-between;
|
|
flex-direction: row;
|
|
padding: 0.375rem 0.5rem;
|
|
}
|
|
.soundboard-grid.list-mode .soundboard-btn .sb-name {
|
|
text-align: left;
|
|
flex: 1;
|
|
font-size: 0.8125rem;
|
|
}
|
|
.soundboard-btn.hidden-sound { opacity: 0.4; }
|
|
/* Eye (hide/unhide) toggle on each tile. Always rendered with a reserved
|
|
width and at low opacity so it never shifts the tile contents on hover. */
|
|
.soundboard-btn .sb-hide-btn {
|
|
font-size: 0.6875rem;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
padding: 0.125rem 0.25rem;
|
|
border-radius: 0.125rem;
|
|
width: 1.125rem;
|
|
height: 1.125rem;
|
|
line-height: 14px;
|
|
text-align: center;
|
|
opacity: 0.35;
|
|
transition: opacity 0.15s, color 0.15s;
|
|
margin-left: auto;
|
|
flex-shrink: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
.soundboard-btn:hover .sb-hide-btn { opacity: 1; }
|
|
.soundboard-btn .sb-hide-btn:hover { color: var(--danger); opacity: 1; }
|
|
/* Hidden tile: the eye icon is the only way back in, so keep it fully visible. */
|
|
.soundboard-btn.hidden-sound .sb-hide-btn { opacity: 1; }
|
|
/* In tile layout the eye sits at the top-right corner without affecting flex flow */
|
|
.soundboard-grid:not(.list-mode):not(.sidebar-mode) .soundboard-btn { position: relative; }
|
|
.soundboard-grid:not(.list-mode):not(.sidebar-mode) .soundboard-btn .sb-hide-btn {
|
|
position: absolute; top: 4px; right: 4px; margin: 0;
|
|
}
|
|
/* Hotkey recording visual */
|
|
.hotkey-recording { animation: hotkey-pulse 0.6s ease infinite alternate; }
|
|
@keyframes hotkey-pulse { from { box-shadow: 0 0 0 2px var(--accent-glow); } to { box-shadow: 0 0 8px 4px var(--accent-glow); } }
|
|
|
|
/* Collapsible sections in the Manage tab sound list */
|
|
.sound-section {
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
.sound-section-label {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
font-size: 0.78em;
|
|
text-transform: uppercase;
|
|
letter-spacing: .06em;
|
|
color: var(--text-muted);
|
|
padding: 0.25rem 0 0.25rem 0;
|
|
list-style: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
.sound-section-label::before {
|
|
content: '\25BE'; /* small down triangle */
|
|
font-size: 0.625rem;
|
|
transition: transform 0.15s;
|
|
}
|
|
details.sound-section:not([open]) .sound-section-label::before {
|
|
content: '\25B8'; /* small right triangle */
|
|
}
|
|
|
|
|
|
.message-body { flex: 1; min-width: 0; }
|
|
|
|
.message-header { display: flex; align-items: baseline; gap: 0.5rem; margin-bottom: 0.125rem; }
|
|
.message-author { font-weight: 600; font-size: 0.875rem; }
|
|
.message-time { font-size: 0.6875rem; color: var(--text-muted); }
|
|
|
|
/* A message in Persian, Arabic or Hebrew reads right to left on its own
|
|
line, so its punctuation lands on the right end; English is unaffected. */
|
|
.message-content { unicode-bidi: plaintext; }
|
|
/* With the interface in Persian every label does the same. The layout
|
|
itself stays left to right. */
|
|
html[lang="fa"] body * { unicode-bidi: plaintext; }
|
|
/* An empty box shows its Persian hint right to left; once something is
|
|
typed, the box follows the text (dir="auto" on the message boxes). */
|
|
html[lang="fa"] :is(input:not([type]), input[type="text"], input[type="search"], textarea):placeholder-shown { direction: rtl; }
|
|
|
|
.message-content {
|
|
font-size: 0.875rem;
|
|
color: var(--text-primary);
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
line-height: 1.45;
|
|
}
|
|
|
|
.message-content a { color: var(--text-link); }
|
|
/* A thin underline so a link reads as one on any palette, not only by its
|
|
colour; a coloured span around it cannot repaint it (#5661). */
|
|
.message-content a:not(.file-download-link) { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.15em; }
|
|
|
|
.message-compact {
|
|
display: flex;
|
|
position: relative;
|
|
/* Left pad matches the leader's avatar column (see --msg-gutter above). */
|
|
padding: 0.0625rem 0.5rem 0.0625rem var(--msg-gutter);
|
|
border-radius: var(--radius-sm);
|
|
transition: background var(--transition);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.message-compact:hover { background: rgba(255,255,255,0.02); box-shadow: var(--msg-glow); }
|
|
|
|
/* Thin horizontal separator between different users' message groups */
|
|
.message-user-sep {
|
|
margin-top: 0.5rem;
|
|
border-top: 1px solid var(--border);
|
|
padding-top: 0.25rem;
|
|
}
|
|
|
|
.message-compact .compact-time {
|
|
position: absolute;
|
|
left: var(--msg-pad-x);
|
|
top: 2px;
|
|
font-size: 0.625rem;
|
|
color: var(--text-muted);
|
|
display: none;
|
|
}
|
|
|
|
/* Only show compact timestamp on real mouse hover */
|
|
@media (hover: hover) {
|
|
.message-compact:hover .compact-time { display: block; }
|
|
}
|
|
|
|
.system-message {
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
font-style: italic;
|
|
text-align: center;
|
|
}
|
|
|
|
.welcome-message {
|
|
padding: 0.625rem 1rem;
|
|
margin: 0.5rem auto;
|
|
max-width: 30rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--accent, #5865f2);
|
|
text-align: center;
|
|
background: var(--bg-secondary, #2b2d31);
|
|
border: 1px solid var(--border-light, #3a3c42);
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
/* A message marked NSFW (app-message-nsfw.js): text, pictures, videos and
|
|
link cards stay blurred under a label until clicked. Text blurs through a
|
|
shadow, so the label on top of it stays sharp. */
|
|
[data-nsfw="1"]:not([data-nsfw-shown]) :is(.message-content, .thread-msg-content) {
|
|
position: relative;
|
|
min-height: 2.25rem;
|
|
overflow: hidden;
|
|
border-radius: var(--radius);
|
|
cursor: pointer;
|
|
user-select: none;
|
|
color: transparent;
|
|
text-shadow: 0 0 12px var(--text-primary);
|
|
}
|
|
[data-nsfw="1"]:not([data-nsfw-shown]) :is(.message-content, .thread-msg-content) a { color: transparent; }
|
|
[data-nsfw="1"]:not([data-nsfw-shown]) :is(.message-content, .thread-msg-content) > * { filter: blur(18px); pointer-events: none; }
|
|
[data-nsfw="1"]:not([data-nsfw-shown]) :is(.message-content, .thread-msg-content)::after {
|
|
content: var(--nsfw-message-label, "NSFW");
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
text-shadow: none;
|
|
background: color-mix(in srgb, var(--bg-tertiary) 55%, transparent);
|
|
}
|
|
|
|
.typing-indicator {
|
|
padding: 0 1.25rem;
|
|
height: 1.25rem;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
/* ── Message Input ─────────────────────────────────────── */
|
|
|
|
.message-input-area {
|
|
display: flex;
|
|
align-items: flex-end;
|
|
/* In a narrow message column (a half-screen browser with both sidebars
|
|
open) the toolbar was squeezing the text box down to a few characters.
|
|
The box keeps a usable width and the toolbar wraps onto its own row
|
|
above it instead. (#5626) */
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
padding: 1rem 1.25rem 1rem;
|
|
position: relative;
|
|
/* Above #fx-layers (z:99990) so theme particles/embers don't float over
|
|
the textarea, send button, and toolbar. The composer is a critical
|
|
interaction surface and must stay visually unobstructed. */
|
|
z-index: 99995;
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
/* Moderation history and warnings (#5759). */
|
|
.mod-cases-modal { max-width: 34rem; width: 100%; }
|
|
.mod-case-reason { margin: 0; padding: 0.5rem 0.75rem; border-left: 3px solid var(--warning, #f0a020); background: var(--bg-tertiary); border-radius: var(--radius-sm); white-space: pre-wrap; }
|
|
.mod-case-input { width: 100%; box-sizing: border-box; resize: vertical; }
|
|
.mod-case-severity { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; font-size: 0.8125rem; }
|
|
.mod-case-summary { margin-bottom: 0.5rem; font-size: 0.8125rem; }
|
|
.mod-case-list { max-height: 50vh; overflow-y: auto; display: flex; flex-direction: column; gap: 0.375rem; margin-bottom: 0.5rem; }
|
|
.mod-case-row { display: flex; gap: 0.5rem; align-items: flex-start; padding: 0.375rem 0.5rem; border-radius: var(--radius-sm); background: var(--bg-tertiary); }
|
|
.mod-case-row.is-revoked { opacity: 0.6; }
|
|
.mod-case-row.is-revoked .mod-case-text { text-decoration: line-through; }
|
|
.mod-case-body { flex: 1; min-width: 0; }
|
|
.mod-case-meta { font-size: 0.75rem; color: var(--text-muted); }
|
|
.mod-case-text { font-size: 0.875rem; white-space: pre-wrap; word-break: break-word; }
|
|
.mod-case-revoked { font-style: italic; }
|
|
.mod-case-note { display: flex; gap: 0.5rem; align-items: flex-end; }
|
|
.mod-case-note[hidden] { display: none; }
|
|
|
|
/* Above the message box in a read-only channel, for those who may post. */
|
|
.read-only-note {
|
|
padding: 0.25rem 1rem 0;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.message-input-area textarea {
|
|
flex: 1;
|
|
min-width: 11rem;
|
|
padding: 0.625rem 0.875rem;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
color: var(--text-primary);
|
|
font-size: 0.875rem;
|
|
resize: none;
|
|
max-height: 7.5rem;
|
|
line-height: 1.4;
|
|
transition: border-color var(--transition);
|
|
}
|
|
|
|
.message-input-area textarea:focus { border-color: var(--accent); }
|
|
.message-input-area textarea::placeholder { color: var(--text-muted); }
|
|
|
|
.btn-send {
|
|
width: 2.625rem;
|
|
height: 2.625rem;
|
|
background: var(--accent);
|
|
color: var(--accent-text);
|
|
border-radius: var(--radius);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: background var(--transition);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.btn-send:hover { background: var(--accent-hover); }
|
|
|
|
/* ── Right Sidebar ─────────────────────────────────────── */
|
|
|
|
.right-sidebar {
|
|
width: var(--right-width);
|
|
min-width: 12.5rem;
|
|
max-width: 25rem;
|
|
background: var(--bg-secondary);
|
|
border-left: 1px solid var(--border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
position: relative;
|
|
transition: width 0.25s ease, min-width 0.25s ease, border-width 0.25s ease, opacity 0.2s ease;
|
|
}
|
|
|
|
/* Collapsed state */
|
|
.right-sidebar.collapsed {
|
|
width: 0 !important;
|
|
min-width: 0 !important;
|
|
border-left-width: 0;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Sidebar collapse arrow: fixed to the right edge of the viewport.
|
|
Always accessible regardless of sidebar state or scroll position. */
|
|
.sidebar-collapse-btn {
|
|
position: fixed;
|
|
right: 0;
|
|
top: 72px; /* near top, where the voice panel header sits */
|
|
transform: none;
|
|
width: 1.25rem;
|
|
height: 2.25rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 0.6875rem;
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-right: none;
|
|
border-radius: 0.375rem 0 0 0.375rem;
|
|
color: var(--text-muted);
|
|
cursor: pointer;
|
|
padding: 0;
|
|
z-index: 100;
|
|
/* `right` is set imperatively in JS when sidebars open/close; animate it
|
|
so the button slides with the panel rather than popping into place. */
|
|
transition: right 0.25s ease, color 0.15s, background 0.15s;
|
|
}
|
|
.sidebar-collapse-btn:hover {
|
|
color: var(--text-primary);
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
/* Users section: fills remaining space and scrolls */
|
|
.right-sidebar-users {
|
|
flex: 1 1 0;
|
|
overflow-y: auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* Resize handle (left edge of right sidebar) */
|
|
.right-sidebar-resize-handle {
|
|
position: absolute;
|
|
top: 0;
|
|
left: -3px;
|
|
width: 0.375rem;
|
|
height: 100%;
|
|
cursor: col-resize;
|
|
z-index: 10;
|
|
background: transparent;
|
|
transition: background 0.15s;
|
|
}
|
|
.right-sidebar-resize-handle:hover,
|
|
.right-sidebar-resize-handle.dragging {
|
|
background: var(--accent);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.panel { padding: 1rem; border-bottom: 1px solid var(--border); }
|
|
|
|
.panel-title {
|
|
font-size: 0.6875rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.8px;
|
|
color: var(--text-muted);
|
|
margin-bottom: 0.625rem;
|
|
}
|
|
|
|
.user-list { display: flex; flex-direction: column; gap: 0.25rem; }
|
|
|
|
.muted-text { font-size: 0.75rem; color: var(--text-muted); font-style: italic; }
|
|
|
|
.user-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0.375rem; /* 4px 6px */
|
|
border-radius: var(--radius-sm);
|
|
position: relative;
|
|
}
|
|
|
|
.user-item:hover {
|
|
background: var(--bg-hover);
|
|
}
|
|
|
|
.user-dot { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--success); flex-shrink: 0; }
|
|
/* Text column beside the avatar: name on its own line, with an optional
|
|
smaller line under it for whatever the person is doing. Previously the name
|
|
sat in the same flex row as the custom status, the activity and the score
|
|
badge, all of which reserved width, so a user with both a status and a game
|
|
saw their username collapse to one character and an ellipsis. */
|
|
.user-item-text {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
gap: 0.0625rem;
|
|
}
|
|
|
|
/* The name line. Role dot/icon and badges sit with the name; nothing else
|
|
competes for this row. */
|
|
.user-item-line {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.375rem;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* The "half line". Deliberately smaller and dimmer than the name so the list
|
|
still scans by username first. */
|
|
.user-item-sub {
|
|
display: flex;
|
|
align-items: center;
|
|
min-width: 0;
|
|
line-height: 1.15;
|
|
}
|
|
|
|
.user-item-name {
|
|
font-size: 0.8125rem;
|
|
color: var(--text-secondary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Inside the sub-line these fill the width instead of reserving a fixed slice
|
|
of the name's row, which is what made them crowd the username before. */
|
|
.user-item-sub .user-status-text,
|
|
.user-item-sub .user-activity {
|
|
max-width: 100%;
|
|
margin-left: 0;
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Role color dot (to the left of the username, inside avatar wrapper) */
|
|
.user-role-dot {
|
|
width: 0.5rem;
|
|
height: 0.5rem;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
border: 1px solid rgba(0,0,0,0.3);
|
|
}
|
|
|
|
/* User tooltip (shown on hover over name) */
|
|
.user-item-tooltip {
|
|
display: none;
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: calc(100% + 6px);
|
|
transform: translateX(-50%);
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius);
|
|
padding: 0.5rem 0.75rem;
|
|
z-index: 1000;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
box-shadow: 0 6px 20px rgba(0,0,0,0.6);
|
|
font-size: 0.75rem;
|
|
color: var(--text-primary);
|
|
max-width: 13.75rem;
|
|
opacity: 1;
|
|
backdrop-filter: none;
|
|
}
|
|
.user-item-tooltip::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border: 5px solid transparent;
|
|
border-top-color: var(--border-light);
|
|
}
|
|
.user-item:hover .user-item-tooltip {
|
|
display: block;
|
|
}
|
|
.tooltip-username {
|
|
font-weight: 700;
|
|
font-size: 0.8125rem;
|
|
margin-bottom: 0.125rem;
|
|
}
|
|
.tooltip-role {
|
|
font-size: 0.6875rem;
|
|
opacity: 0.8;
|
|
}
|
|
.tooltip-status {
|
|
font-size: 0.6875rem;
|
|
color: var(--text-muted);
|
|
font-style: italic;
|
|
margin-top: 0.125rem;
|
|
}
|
|
|
|
/* â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•
|
|
PROFILE POPUP (Discord-style mini profile card)
|
|
â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â• */
|
|
|
|
.profile-popup {
|
|
position: fixed;
|
|
/* Appended to <body>, so it stacks against the page rather than inside the
|
|
member list that opened it. Below 900px the right sidebar becomes a fixed
|
|
overlay at 100000 with its backdrop at 99996, which left this popup at
|
|
10001 rendering behind both (#5465). Same layer the member-list gear menu
|
|
uses for exactly this reason (#5462). */
|
|
z-index: 100010;
|
|
width: 20rem;
|
|
/* Translucent, theme-aware glass. Colours come from CSS vars so it tracks
|
|
the active theme in both light and dark. */
|
|
background: color-mix(in srgb, var(--bg-secondary) 65%, transparent);
|
|
backdrop-filter: blur(20px) saturate(1.3);
|
|
-webkit-backdrop-filter: blur(20px) saturate(1.3);
|
|
border: 1px solid color-mix(in srgb, var(--border-light) 50%, transparent);
|
|
border-radius: var(--radius);
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.4);
|
|
overflow: hidden;
|
|
animation: profile-popup-in 0.18s ease-out;
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Hover-over variant: a non-interactive preview. The base card is already
|
|
translucent glass, so this only strips the interactive bits. */
|
|
.profile-popup-hover {
|
|
animation: profile-popup-hover-in 0.15s ease-out;
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
pointer-events: none; /* it is a tooltip, not a card */
|
|
}
|
|
.profile-popup-hover .profile-popup-banner { opacity: 0.7; }
|
|
.profile-popup-hover .profile-popup-close { display: none; }
|
|
.profile-popup-hover .profile-popup-actions { display: none; }
|
|
|
|
/* Fade-out for hover popups */
|
|
.profile-popup-fading {
|
|
opacity: 0 !important;
|
|
transform: translateY(4px) !important;
|
|
pointer-events: none !important;
|
|
transition: opacity 0.2s ease, transform 0.2s ease !important;
|
|
}
|
|
|
|
@keyframes profile-popup-hover-in {
|
|
from { opacity: 0; transform: translateY(4px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
@keyframes profile-popup-in {
|
|
from { opacity: 0; transform: scale(0.95) translateY(6px); }
|
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
|
}
|
|
|
|
.profile-popup-banner {
|
|
height: 3.75rem;
|
|
position: relative;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.profile-popup-close {
|
|
position: absolute;
|
|
top: 6px; right: 8px;
|
|
background: none; border: none;
|
|
color: var(--text-muted);
|
|
font-size: 1.125rem;
|
|
cursor: pointer;
|
|
line-height: 1;
|
|
padding: 0.125rem 0.375rem;
|
|
border-radius: 0.25rem;
|
|
}
|
|
.profile-popup-close:hover { background: rgba(255,255,255,0.1); color: var(--text-primary); }
|
|
|
|
.profile-popup-avatar-wrapper {
|
|
position: relative;
|
|
width: 4.5rem; height: 4.5rem;
|
|
margin: -2.25rem 0 0 1rem;
|
|
}
|
|
|
|
.profile-popup-avatar {
|
|
width: 4.5rem; height: 4.5rem;
|
|
border-radius: 50%;
|
|
object-fit: cover;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 1.75rem;
|
|
font-weight: 700;
|
|
color: #fff;
|
|
}
|
|
.profile-popup-avatar.avatar-hexagon { border-radius: 0; clip-path: polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%); }
|
|
.profile-popup-avatar.avatar-squircle { border-radius: 22%; }
|
|
.profile-popup-avatar.avatar-diamond { border-radius: 0; clip-path: polygon(50% 0%,100% 50%,50% 100%,0% 50%); }
|
|
.profile-popup-avatar.avatar-shield { border-radius: 0; clip-path: polygon(50% 0%,100% 10%,100% 65%,50% 100%,0% 65%,0% 10%); }
|
|
|
|
.profile-popup-avatar-fallback {
|
|
background-color: var(--accent);
|
|
}
|
|
|
|
.profile-popup-status-dot {
|
|
position: absolute;
|
|
bottom: 2px; right: 2px;
|
|
width: 1rem; height: 1rem;
|
|
border-radius: 50%;
|
|
background: var(--success);
|
|
border: 3px solid var(--bg-primary);
|
|
}
|
|
.profile-popup-status-dot.away { background: var(--warning); }
|
|
.profile-popup-status-dot.dnd { background: var(--danger); }
|
|
.profile-popup-status-dot.invisible { background: var(--text-muted); }
|
|
|
|
.profile-popup-body {
|
|
padding: 0.625rem 1rem 1rem;
|
|
background: transparent;
|
|
}
|
|
|
|
.profile-popup-names {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.0625rem;
|
|
margin-bottom: 0.375rem;
|
|
}
|
|
|
|
.profile-popup-displayname {
|
|
font-size: 1.125rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.profile-popup-nickname {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--accent);
|
|
line-height: 1.2;
|
|
margin-bottom: 0.125rem;
|
|
}
|
|
|
|
.profile-popup-username {
|
|
font-size: 0.8125rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.profile-popup-status-text {
|
|
font-size: 0.8125rem;
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
padding: 0.25rem 0.5rem;
|
|
background: var(--bg-secondary);
|
|
border-radius: var(--radius-sm);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.profile-popup-bio {
|
|
font-size: 0.8125rem;
|
|
color: var(--text-secondary);
|
|
line-height: 1.4;
|
|
margin-bottom: 0.25rem;
|
|
word-break: break-word;
|
|
}
|
|
|
|
.profile-bio-empty {
|
|
color: var(--text-muted);
|
|
font-style: italic;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.profile-bio-toggle {
|
|
background: none; border: none;
|
|
color: var(--accent);
|
|
cursor: pointer;
|
|
font-size: 0.75rem;
|
|
padding: 0.125rem 0;
|
|
}
|
|
.profile-bio-toggle:hover { text-decoration: underline; }
|
|
|
|
.profile-popup-divider {
|
|
height: 0.0625rem;
|
|
background: var(--border);
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
.profile-popup-section-label {
|
|
font-size: 0.6875rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
color: var(--text-muted);
|
|
margin-bottom: 0.25rem;
|
|
letter-spacing: 0.03em;
|
|
}
|
|
|
|
.profile-popup-roles,
|
|
.user-profile-groups-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.25rem;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.profile-popup-role {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
padding: 0.125rem 0.5rem;
|
|
font-size: 0.75rem;
|
|
border: 1px solid;
|
|
border-radius: 0.75rem;
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.user-group-name {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
}
|
|
|
|
.user-group-channel-info {
|
|
padding: 0.1rem 0.3rem;
|
|
border: 1px solid;
|
|
border-radius: 50%;
|
|
font-weight: 600;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.profile-role-dot {
|
|
width: 0.5rem; height: 0.5rem;
|
|
border-radius: 50%;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.profile-popup-join-date {
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
/* Birthdays: the 🎉 in front of a name on the day, and the shower that falls
|
|
past the avatar when a profile card opens (_rainBirthdayConfetti). */
|
|
.profile-popup-displayname .birthday-mark { margin-right: 0.25em; }
|
|
.message-header .birthday-mark { margin-right: -0.25rem; }
|
|
.birthday-confetti {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
z-index: 100011; /* above the profile card */
|
|
line-height: 1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.profile-popup-actions {
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.profile-popup-action-btn {
|
|
width: 100%;
|
|
padding: 0.5rem 0.75rem;
|
|
border: none;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
font-size: 0.8125rem;
|
|
font-weight: 600;
|
|
transition: background var(--transition), transform 0.1s;
|
|
}
|
|
.profile-popup-action-btn:active { transform: scale(0.98); }
|
|
|
|
.profile-dm-btn {
|
|
background: var(--accent);
|
|
color: var(--accent-text);
|
|
}
|
|
.profile-dm-btn:hover { background: var(--accent-hover); }
|
|
|
|
.profile-edit-btn {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
border: 1px solid var(--border-light) !important;
|
|
}
|
|
.profile-edit-btn:hover { background: var(--bg-hover); }
|
|
|
|
.profile-gear-btn {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-secondary);
|
|
margin-top: 0.375rem;
|
|
border: 1px solid var(--border-light) !important;
|
|
}
|
|
.profile-gear-btn:hover { background: var(--bg-hover); color: var(--text-primary); }
|
|
|
|
|
|
/* Silent messages (app-silent.js): a muted bell after the text, and the
|
|
message box toggle lit like the Send button while it is on. */
|
|
.silent-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
vertical-align: -1px;
|
|
margin-left: 0.375rem;
|
|
color: var(--text-muted);
|
|
opacity: 0.75;
|
|
cursor: default;
|
|
user-select: none;
|
|
}
|
|
.silent-tag svg { display: block; }
|
|
.btn-silent.active,
|
|
.btn-silent.active:hover {
|
|
background: var(--accent);
|
|
color: var(--accent-text);
|
|
}
|