diff --git a/src/app/components/message/layout/Channel.css.ts b/src/app/components/message/layout/Channel.css.ts index aa197f9c..ce194186 100644 --- a/src/app/components/message/layout/Channel.css.ts +++ b/src/app/components/message/layout/Channel.css.ts @@ -169,6 +169,10 @@ globalStyle( `${ChannelRow}[data-bubble="true"][data-own="false"] ${ChannelMessageBody}`, { borderRadius: `${config.radii.R500} ${config.radii.R500} ${config.radii.R500} ${toRem(4)}`, + // Peer (not-own) bubble bg — matches Stream layout's `peerBg` + // variant. Covers channels main timeline AND thread drawer + // (both pass `headerInBubble`, so `data-bubble="true"` fires). + backgroundColor: 'var(--vojo-peer-bubble-bg)', } ); diff --git a/src/app/components/message/layout/Stream.tsx b/src/app/components/message/layout/Stream.tsx index 6da75a66..0a9c8664 100644 --- a/src/app/components/message/layout/Stream.tsx +++ b/src/app/components/message/layout/Stream.tsx @@ -32,6 +32,10 @@ export type StreamLayoutProps = { dotColor: string; dotOpacity: number; isOwn?: boolean; + // Peer (not-own) bubble bg — caller passes `!isOwn` so every + // «чужое» сообщение reshades to `--vojo-peer-bubble-bg`. Applies + // in 1-1 DMs, groups, channels alike. No effect for own messages. + peerBg?: boolean; compact?: boolean; header?: ReactNode; railStart?: boolean; @@ -100,6 +104,7 @@ export const StreamLayout = as<'div', StreamLayoutProps>( dotColor, dotOpacity, isOwn, + peerBg, compact, header, railStart, @@ -169,6 +174,7 @@ export const StreamLayout = as<'div', StreamLayoutProps>( className={css.StreamBubble({ own: !!isOwn, compact: !!compact, + peerBg: !!peerBg, mediaMode: !!mediaMode, })} ref={bubbleRef} diff --git a/src/app/components/message/layout/layout.css.ts b/src/app/components/message/layout/layout.css.ts index 9f422639..07b92ea8 100644 --- a/src/app/components/message/layout/layout.css.ts +++ b/src/app/components/message/layout/layout.css.ts @@ -283,7 +283,7 @@ export const StreamRail = style({ left: '50%', transform: 'translateX(-50%)', width: StreamRailLineWidth, - background: color.Surface.Container, + background: 'var(--vojo-timeline-rail)', pointerEvents: 'none', zIndex: 0, }); @@ -452,6 +452,14 @@ export const StreamBubble = recipe({ paddingRight: toRem(15), }, }, + // Peer (not-own) bubble bg — differentiation between «я» and + // «не я» across every room class. Media rows neutralize this via + // the `peerBg + mediaMode` compound below (order-independent). + peerBg: { + true: { + backgroundColor: 'var(--vojo-peer-bubble-bg)', + }, + }, // Image messages: bubble becomes a transparent shell so the // StreamMediaImage child supplies the visible chrome instead. // `display: block, width: 100%` (NOT fit-content) so the bubble has a @@ -471,9 +479,21 @@ export const StreamBubble = recipe({ }, }, }, + // Compound overrides — emitted after all variant classes, so they + // win the cascade regardless of variant declaration order. Keeps + // peer image / video bubbles transparent (the StreamMediaImage + // child supplies the chrome) even though `peerBg` would otherwise + // paint `--vojo-peer-bubble-bg` underneath. + compoundVariants: [ + { + variants: { peerBg: true, mediaMode: true }, + style: { backgroundColor: 'transparent' }, + }, + ], defaultVariants: { own: false, compact: false, + peerBg: false, mediaMode: false, }, }); @@ -631,7 +651,7 @@ export const StreamDayLineWrap = style({ export const StreamDayLineSegment = style({ flex: 1, height: 1, - background: color.Surface.Container, + background: 'var(--vojo-timeline-rail)', minWidth: toRem(8), }); diff --git a/src/app/features/room/message/CallMessage.tsx b/src/app/features/room/message/CallMessage.tsx index 9b72004e..e6eb1c33 100644 --- a/src/app/features/room/message/CallMessage.tsx +++ b/src/app/features/room/message/CallMessage.tsx @@ -109,6 +109,7 @@ export function CallMessage({ const senderId = aggregate.anchorSenderId; const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId(); + const peerBg = !isOwnMessage; const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId; @@ -220,6 +221,7 @@ export function CallMessage({ dotColor={dot.color} dotOpacity={dot.opacity} isOwn={isOwnMessage} + peerBg={peerBg} compact={isMobile} railStart={streamRailStart} railEnd={streamRailEnd} diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index f312ba04..14df89cb 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -761,7 +761,7 @@ export const Message = as<'div', MessageProps>( const [menuAnchor, setMenuAnchor] = useState(); const [emojiBoardAnchor, setEmojiBoardAnchor] = useState(); - const isOwnMessage = senderId === mx.getUserId(); + const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId(); const senderDisplayName = getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId; @@ -774,6 +774,7 @@ export const Message = as<'div', MessageProps>( const dot = useDotColor(room, mEvent, true, hideReadReceipts); const screenSize = useScreenSizeContext(); const isMobile = screenSize === ScreenSize.Mobile; + const peerBg = !isOwnMessage; // msgType comes from the parent — RoomTimeline reads // `mEvent.getContent().msgtype` synchronously and re-evaluates inside @@ -1200,6 +1201,7 @@ export const Message = as<'div', MessageProps>( dotColor={dot.color} dotOpacity={dot.opacity} isOwn={isOwnMessage} + peerBg={peerBg} compact={isMobile} railStart={streamRailStart} railEnd={streamRailEnd} diff --git a/src/app/features/room/message/SyslineMessage.tsx b/src/app/features/room/message/SyslineMessage.tsx index d60620a7..fc073f3b 100644 --- a/src/app/features/room/message/SyslineMessage.tsx +++ b/src/app/features/room/message/SyslineMessage.tsx @@ -58,6 +58,7 @@ export function SyslineMessage({ const senderId = mEvent.getSender() ?? ''; const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId(); + const peerBg = !isOwnMessage; const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId; @@ -112,6 +113,7 @@ export function SyslineMessage({ dotColor={dot.color} dotOpacity={dot.opacity} isOwn={isOwnMessage} + peerBg={peerBg} compact={isMobile} railStart={streamRailStart} railEnd={streamRailEnd} diff --git a/src/index.css b/src/index.css index 550614f9..4a37bea6 100644 --- a/src/index.css +++ b/src/index.css @@ -21,6 +21,15 @@ default is the light-theme value; `.dark-theme` overrides below. */ --vojo-horseshoe-void: #d6d6e3; + /* Peer (not-own) bubble bg — every «чужое» message reshades to + this, in 1-1 DMs, groups, channels alike. Light: slight off-white + step from #ffffff. Dark override below. */ + --vojo-peer-bubble-bg: #f5f5fa; + /* Stream timeline rail (vertical line through dots) + day-divider + horizontal segments. Light: hairline cool-grey; dark overrides + below. */ + --vojo-timeline-rail: #e8e8f2; + --font-emoji: 'Twemoji_DISABLED'; --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; } @@ -39,6 +48,9 @@ --vojo-horseshoe-void: #090909; + --vojo-peer-bubble-bg: #090909; + --vojo-timeline-rail: #000000; + --font-secondary: 'InterVariable', var(--font-emoji), sans-serif; }