From 97b335773b1573e65c9cc7c26d64f3cd834f3120 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 23 May 2026 23:52:58 -0400 Subject: [PATCH] =?UTF-8?q?ui:=20visual=20polish=20=E2=80=94=20animations,?= =?UTF-8?q?=20icons,=20and=20interaction=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Spin animation on ⟳ delivery status during SENDING/ENCRYPTING states - Pulsing ● dot on PTT LIVE badge (pttLivePulse keyframe) - Read receipt pill: hover scale/opacity transition, symmetric padding - PiP resize handles: larger dots (5px), wider hit area (24px), higher contrast - ForwardMessageDialog: position:relative on scroll container, spinner overlay 0.35 opacity - Boot sequence: 45ms interval (was 65ms), brighter ESC hint (0.55 opacity) - Location button: Icons.Pin → Icons.SpaceGlobe (globe icon) Co-Authored-By: Claude Sonnet 4.6 --- src/app/components/CallEmbedProvider.tsx | 17 +++++++++-------- .../read-receipt-avatars/ReadReceiptAvatars.tsx | 13 ++++++++++++- src/app/features/call/CallControls.tsx | 16 +++++++++++++++- src/app/features/room/RoomInput.tsx | 2 +- .../room/message/ForwardMessageDialog.tsx | 4 ++-- src/app/features/room/message/Message.tsx | 11 ++++++++++- src/app/styles/Animations.css.ts | 11 +++++++++++ src/index.css | 10 ++++++++++ src/lotus-boot.ts | 4 ++-- 9 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/app/components/CallEmbedProvider.tsx b/src/app/components/CallEmbedProvider.tsx index 53c304b44..6fa50101c 100644 --- a/src/app/components/CallEmbedProvider.tsx +++ b/src/app/components/CallEmbedProvider.tsx @@ -737,15 +737,16 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) { const s = corner.includes('s'); const e2 = corner.includes('e'); const dots = [ - [2, 2], - [2, 7], - [7, 2], + [3, 3], + [3, 10], + [10, 3], ].map(([a, b]) => ({ position: 'absolute' as const, - width: 4, - height: 4, + width: 5, + height: 5, borderRadius: '50%', - background: 'rgba(255,255,255,0.45)', + background: 'rgba(255,255,255,0.65)', + boxShadow: '0 0 3px rgba(0,0,0,0.4)', [s ? 'bottom' : 'top']: a, [e2 ? 'right' : 'left']: b, })); @@ -756,8 +757,8 @@ export function CallEmbedProvider({ children }: CallEmbedProviderProps) { onClick={(ev) => ev.stopPropagation()} style={{ position: 'absolute', - width: '18px', - height: '18px', + width: '24px', + height: '24px', [s ? 'bottom' : 'top']: 0, [e2 ? 'right' : 'left']: 0, cursor: `${corner}-resize`, diff --git a/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx b/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx index 185779c43..a784e1ead 100644 --- a/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx +++ b/src/app/components/read-receipt-avatars/ReadReceiptAvatars.tsx @@ -62,6 +62,7 @@ export function ReadReceiptAvatars({ onClick={() => setOpen(true)} title={tooltipNames} aria-label={tooltipNames} + className="receipt-pill-btn" style={{ background: 'none', border: 'none', @@ -72,6 +73,16 @@ export function ReadReceiptAvatars({ display: 'flex', alignItems: 'center', gap: '4px', + opacity: 0.85, + transition: 'opacity 0.15s, transform 0.15s', + }} + onMouseEnter={(e) => { + e.currentTarget.style.opacity = '1'; + e.currentTarget.style.transform = 'scale(1.04)'; + }} + onMouseLeave={(e) => { + e.currentTarget.style.opacity = '0.85'; + e.currentTarget.style.transform = 'scale(1)'; }} > {/* Pill wrapper ensures visibility on any wallpaper/background */} @@ -85,7 +96,7 @@ export function ReadReceiptAvatars({ border: lotusTerminal ? '1px solid rgba(0,212,255,0.30)' : '1px solid transparent', boxShadow: lotusTerminal ? '0 0 10px rgba(0,212,255,0.12)' : 'none', borderRadius: '999px', - padding: '2px 6px 2px 2px', + padding: '2px 6px', gap: '0px', }} > diff --git a/src/app/features/call/CallControls.tsx b/src/app/features/call/CallControls.tsx index e3a0059a0..18df279b9 100644 --- a/src/app/features/call/CallControls.tsx +++ b/src/app/features/call/CallControls.tsx @@ -218,7 +218,21 @@ export function CallControls({ callEmbed }: CallControlsProps) { fontFamily: 'JetBrains Mono, monospace', }} > - {pttActive ? '● LIVE' : `PTT — Hold ${pttKeyLabel}`} + {pttActive ? ( + <> + + ● + + {' LIVE'} + + ) : ( + `PTT — Hold ${pttKeyLabel}` + )} ) : ( diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index f22ffde44..c069a46e0 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -879,7 +879,7 @@ export const RoomInput = forwardRef( ... ) : ( - + )} diff --git a/src/app/features/room/message/ForwardMessageDialog.tsx b/src/app/features/room/message/ForwardMessageDialog.tsx index 26f51e87c..3202bd054 100644 --- a/src/app/features/room/message/ForwardMessageDialog.tsx +++ b/src/app/features/room/message/ForwardMessageDialog.tsx @@ -162,7 +162,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) { ✓ Forwarded to {sentTo} ) : ( - + {filtered.slice(0, 60).map((room) => ( @@ -195,7 +195,7 @@ export function ForwardMessageDialog({ mEvent, onClose }: Props) { style={{ position: 'absolute', inset: 0, - background: 'rgba(0,0,0,0.18)', + background: 'rgba(0,0,0,0.35)', borderRadius: config.radii.R500, }} > diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx index 325498b0f..c736f2c59 100644 --- a/src/app/features/room/message/Message.tsx +++ b/src/app/features/room/message/Message.tsx @@ -58,6 +58,7 @@ import { MessageLayout, MessageSpacing } from '../../../state/settings'; import { useMatrixClient } from '../../../hooks/useMatrixClient'; import { useRecentEmoji } from '../../../hooks/useRecentEmoji'; import * as css from './styles.css'; +import { SendingSpinClass } from '../../../styles/Animations.css'; import { EventReaders } from '../../../components/event-readers'; import { ReadReceiptAvatars } from '../../../components/read-receipt-avatars'; import { useReadPositions } from '../ReadPositionsContext'; @@ -127,7 +128,15 @@ function DeliveryStatus({ : {}), }} > - {icon} + + {icon} + ); } diff --git a/src/app/styles/Animations.css.ts b/src/app/styles/Animations.css.ts index c6bdc5652..4e64f110b 100644 --- a/src/app/styles/Animations.css.ts +++ b/src/app/styles/Animations.css.ts @@ -1,6 +1,11 @@ import { keyframes, style } from '@vanilla-extract/css'; import { color, toRem } from 'folds'; +const spin = keyframes({ + from: { transform: 'rotate(0deg)' }, + to: { transform: 'rotate(360deg)' }, +}); + const wobble = keyframes({ '0%': { transform: 'translateX(0) rotateZ(0deg)', @@ -45,3 +50,9 @@ export const CallAvatarAnimation = style({ animation: `${wobble} 2000ms ease-in-out, ${glowPulse} 2000ms ease-out`, animationIterationCount: 'infinite', }); + +export const SendingSpinClass = style({ + display: 'inline-block', + animation: `${spin} 900ms linear infinite`, + transformOrigin: 'center', +}); diff --git a/src/index.css b/src/index.css index 5dc114471..f9323921d 100644 --- a/src/index.css +++ b/src/index.css @@ -156,6 +156,16 @@ audio:not([controls]) { display: none !important; } +@keyframes pttLivePulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.25; + } +} + /* Fix Firefox rendering lists that have empty items with those items collapsed in on eachother */ li p::before { content: ''; diff --git a/src/lotus-boot.ts b/src/lotus-boot.ts index 8187b0ad3..391352b79 100644 --- a/src/lotus-boot.ts +++ b/src/lotus-boot.ts @@ -65,7 +65,7 @@ export function runLotusBootSequence(force = false): void { 'bottom:1.5rem', 'right:2rem', 'font-size:0.68rem', - 'color:rgba(0,255,136,0.35)', + 'color:rgba(0,255,136,0.55)', "font-family:'JetBrains Mono','Courier New',monospace", 'letter-spacing:0.08em', 'user-select:none', @@ -105,5 +105,5 @@ export function runLotusBootSequence(force = false): void { text += `${BOOT_MESSAGES[i]}\n`; pre.textContent = text; i++; - }, 65); + }, 45); }