/* Message group layout */ .group { padding: 0; margin-top: 1.0625rem; position: relative; user-select: text; -webkit-user-select: text; word-break: break-word; } .contentColumn { min-width: 0; display: flex; flex-direction: column; } /* Avatar positioned in the gutter of the first message */ .avatarSlot { position: absolute; left: 16px; cursor: pointer; } /* --- Reply context (above message) --- */ .replyContext { display: flex; align-items: center; gap: 4px; position: relative; margin-bottom: 4px; margin-top: 2px; font-size: 0.875rem; line-height: 1.125rem; cursor: pointer; min-width: 0; overflow: visible; } .replyContext:hover .replyText { color: var(--text-primary); } /* L-shaped spine: vertical arm goes down to above the avatar, horizontal arm goes right to reply content */ .replySpine { position: absolute; left: -36px; top: 50%; bottom: 0; width: 33px; border-left: 2px solid var(--text-muted); border-top: 2px solid var(--text-muted); border-top-left-radius: 6px; box-sizing: border-box; pointer-events: none; } .replyAuthor { font-weight: 600; font-size: 0.8125rem; color: var(--text-primary); white-space: nowrap; flex-shrink: 0; } .replyText { color: var(--text-muted); font-size: 0.8125rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .replyContentMissing { color: var(--text-muted); font-size: 0.8125rem; font-style: italic; padding-left: 4px; } /* --- Header --- */ .header { display: flex; align-items: baseline; gap: 8px; min-height: 1.375rem; line-height: 1.375rem; } .username { font-weight: 600; font-size: 1rem; color: var(--text-primary); cursor: pointer; } .username:hover { text-decoration: underline; } .timestamp { font-size: 0.75rem; color: var(--text-muted); font-weight: 400; } /* --- Individual message content --- */ .messageContent { position: relative; padding-left: 72px; padding-right: 48px; } .messageContent:hover { background-color: var(--background-modifier-hover); } /* Mentioned-row highlight — copies Fluxer's exact golden tint. A 2px ::before bar sits on the inline-start edge; the row gets a low-alpha gold background, nudged slightly on hover. Fires when the current user is in `m.mentions.user_ids` or when `m.mentions.room` is set. */ .messageMentioned::before { content: ''; position: absolute; inset-block: 0; inset-inline-start: 0; width: 2px; background-color: rgb(234 197 50); pointer-events: none; } .messageMentioned { background-color: rgb(234 197 50 / 0.1); } .messageMentioned:hover { background-color: rgb(234 197 50 / 0.14); } /* First message includes the avatar + header row */ .firstMessage { padding-top: 2px; } /* Compact timestamp: sits in the avatar gutter, visible on hover */ .compactTimestamp { position: absolute; left: 0; width: 72px; text-align: center; top: 0; font-size: 0.6875rem; line-height: 1.375rem; color: var(--text-muted); opacity: 0; pointer-events: none; } .messageContent:hover .compactTimestamp { opacity: 1; } /* --- Message text --- */ .text { font-size: 1rem; line-height: var(--message-line-height, 1.375rem); color: var(--text-primary); word-wrap: break-word; white-space: pre-wrap; } /* "(edited)" tag shown inline next to edited messages. Matches the fluxer treatment — small, muted, one-space offset. */ .editedTag { margin-left: 0.25rem; font-size: 0.625rem; color: var(--text-tertiary); user-select: none; vertical-align: baseline; } /* --- Encrypted placeholder --- */ .encryptedPlaceholder { display: inline-flex; align-items: center; gap: 6px; font-size: 0.875rem; line-height: var(--message-line-height, 1.375rem); color: var(--text-muted); font-style: italic; } /* Tombstone for a redacted message — rendered by MessageGroup when `message.isDeleted` is true. Mirrors the encrypted placeholder's visual language (muted italic + leading icon) so the two states feel like siblings. */ .deletedPlaceholder { display: inline-flex; align-items: center; gap: 6px; font-size: 0.875rem; line-height: var(--message-line-height, 1.375rem); color: var(--text-muted); font-style: italic; } /* --- Attachments --- */ .attachments { margin-top: 4px; } .imageAttachment { border-radius: var(--radius-lg); cursor: pointer; display: block; /* Belt-and-braces: the inline `maxWidth` style caps the image dimensionally (so we don't upscale tiny images), but on narrow viewports a 400px image would still overflow the parent's horizontal padding and bleed into the screen edge. `max-width: 100%` keeps it inside the message's content box regardless of the absolute pixel cap. `height: auto` preserves aspect ratio when width is the constraint. */ max-width: 100%; height: auto; } /* ── Spoiler attachments ───────────────────────────────────── Wrapper lets the "SPOILER" cover button overlay the image via absolute positioning without reflowing the message. The image itself gets a blur filter while covered so even partial glimpses don't give away the content. */ .spoilerableWrap { position: relative; display: inline-block; max-width: 100%; } .imageAttachmentBlurred { filter: blur(44px); transform: scale(1); /* Prevent blurred edges from bleeding outside the rounded corners of the wrapper. */ clip-path: inset(0 round var(--radius-lg)); } .spoilerCover { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.45); border: none; border-radius: var(--radius-lg); cursor: pointer; transition: background-color 0.12s; padding: 0; } .spoilerCover:hover { background-color: rgba(0, 0, 0, 0.6); } .spoilerCoverLabel { padding: 8px 16px; background-color: rgba(0, 0, 0, 0.75); border-radius: 999px; color: #fff; font-size: 0.8125rem; font-weight: 800; letter-spacing: 0.08em; pointer-events: none; } .fileAttachment { display: inline-flex; align-items: center; padding: 8px 12px; background-color: var(--background-secondary); border-radius: var(--radius-lg); color: var(--text-link); font-size: 0.875rem; text-decoration: none; margin-top: 4px; } .fileAttachment:hover { text-decoration: underline; } /* --- Reactions --- */ .reactions { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; } .reactionChip { display: inline-flex; align-items: center; gap: 4px; padding: 0.125rem 0.375rem; border-radius: var(--radius-lg); background-color: color-mix(in srgb, var(--brand-primary) 36%, var(--background-secondary) 64%); border: 1px solid transparent; cursor: pointer; font-size: 0.875rem; color: var(--text-primary); transition: background-color 0.15s; } .reactionChip:hover { background-color: var(--background-modifier-hover); } .reactionMe { border-color: var(--brand-primary); background-color: rgba(88, 101, 242, 0.15); } .reactionCount { font-size: 16px; color: var(--text-secondary); font-weight: 600; } /* Emoji glyph inside a reaction chip — shared between the custom path and the unicode TwemojiImg path so both chip styles have identical box sizing. `rem` units scale with the user's font size instead of hard-locking to 16px. */ .reactionEmoji { width: 1.25rem; height: 1.25rem; margin: 0.125rem 0; object-fit: contain; vertical-align: middle; } /* Custom MSC2545 emoji reaction — rendered as an inline image in the chip. Matched to the Twemoji size used for unicode reactions so both styles of chip look the same height. Animated GIF / WebP emojis play automatically because it's a normal . */ .reactionCustomEmoji { width: 16px; height: 16px; object-fit: contain; display: inline-block; vertical-align: middle; } /* ── Hover gutter timestamp ────────────────────────────────────── Follow-up messages in a sender group don't have an avatar / name header — the timestamp lives in the left gutter (where the avatar would be) and only fades in on hover, matching the new UI. */ .gutterTimestamp { position: absolute; top: 4px; left: 16px; width: 56px; font-size: 11px; color: var(--text-tertiary); text-align: right; padding-right: 8px; box-sizing: border-box; opacity: 0; transition: opacity 0.1s ease; pointer-events: none; user-select: none; font-variant-numeric: tabular-nums; } .messageContent:hover .gutterTimestamp, .messageContent.actionBarForceVisible .gutterTimestamp, :global(.messageHoverable):hover .gutterTimestamp { opacity: 1; }