diff --git a/packages/web/src/components/modals/UserProfileModal.tsx b/packages/web/src/components/modals/UserProfileModal.tsx index 83116d26..3224cf98 100644 --- a/packages/web/src/components/modals/UserProfileModal.tsx +++ b/packages/web/src/components/modals/UserProfileModal.tsx @@ -8,7 +8,7 @@ import { useUIStore } from '../../stores/uiStore'; import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; import { useSocialStore, type TaggedFriend, type TaggedFriendRequest } from '../../stores/socialStore'; import { useAuthStore } from '../../stores/authStore'; -import { getAvatarGradient, getSpaceGradient, adjustColor } from '../../utils/gradients'; +import { getAvatarGradient, getSpaceGradient, adjustColor, mutedGradient } from '../../utils/gradients'; import { parseFederatedUsername, isSelf, canonicalUserMatch } from '../../utils/identity'; import { loadFederatedMutuals, type TaggedMutualFriend, type MutualSpace } from '../../utils/mutuals'; @@ -149,8 +149,11 @@ export function UserProfileModal() { ? (user.banner.startsWith('http') ? user.banner : profileApi.uploads.url(user.banner)) : null; const bannerFallback = user.accentColor - ? `linear-gradient(135deg, ${user.accentColor}, ${adjustColor(user.accentColor, -40)})` - : getAvatarGradient(user.homeUserId ?? user.id, displayName, user.avatarColor).gradient; + ? mutedGradient(user.accentColor, adjustColor(user.accentColor, -40)) + : (() => { + const g = getAvatarGradient(user.homeUserId ?? user.id, displayName, user.avatarColor); + return mutedGradient(g.from, g.to); + })(); const handleSendMessage = async () => { try { @@ -236,7 +239,7 @@ export function UserProfileModal() { className="h-[100px] flex-shrink-0 relative" style={bannerSrc ? { backgroundImage: `url(${bannerSrc})`, backgroundSize: 'cover', backgroundPosition: 'center' } - : { background: bannerFallback, opacity: 0.6 } + : { background: bannerFallback } } > {/* Close button */} diff --git a/packages/web/src/components/modals/settingsPanels/AccountPanel.tsx b/packages/web/src/components/modals/settingsPanels/AccountPanel.tsx index a1e2b287..0a723765 100644 --- a/packages/web/src/components/modals/settingsPanels/AccountPanel.tsx +++ b/packages/web/src/components/modals/settingsPanels/AccountPanel.tsx @@ -3,7 +3,7 @@ import { useAuthStore } from '../../../stores/authStore'; import { Avatar } from '../../ui/Avatar'; import { ImageCropModal } from '../../ui/ImageCropModal'; import { api } from '../../../api/client'; -import { getAvatarGradient, adjustColor, AVATAR_GRADIENT_MAP, BANNER_COLOR_PRESETS } from '../../../utils/gradients'; +import { getAvatarGradient, adjustColor, mutedGradient, AVATAR_GRADIENT_MAP, BANNER_COLOR_PRESETS } from '../../../utils/gradients'; import { AVATAR_COLORS } from '@backspace/shared'; import type { User, UserStatus, AvatarColor } from '@backspace/shared'; @@ -86,10 +86,13 @@ export function AccountPanel() { : null; const displayAvatarSrc = avatarPreview ?? (avatarFilename === '' ? null : currentAvatarSrc); - // Banner fallback: accent gradient or avatar gradient + // Banner fallback: accent gradient or avatar gradient (alpha baked into gradient colors) const bannerFallback = effectiveAccent - ? `linear-gradient(135deg, ${effectiveAccent}, ${adjustColor(effectiveAccent, -40)})` - : getAvatarGradient(user.homeUserId ?? user.id, effectiveDisplayName, effectiveAvatarColor).gradient; + ? mutedGradient(effectiveAccent, adjustColor(effectiveAccent, -40)) + : (() => { + const g = getAvatarGradient(user.homeUserId ?? user.id, effectiveDisplayName, effectiveAvatarColor); + return mutedGradient(g.from, g.to); + })(); // ── File selection handlers ── const handleAvatarSelect = (e: React.ChangeEvent) => { @@ -218,7 +221,7 @@ export function AccountPanel() { className="h-[80px]" style={displayBannerSrc ? { backgroundImage: `url(${displayBannerSrc})`, backgroundSize: 'cover', backgroundPosition: 'center' } - : { background: bannerFallback, opacity: 0.6 } + : { background: bannerFallback } } /> {/* Avatar + info */} @@ -339,7 +342,11 @@ export function AccountPanel() { className="w-full h-full" style={displayBannerSrc ? { backgroundImage: `url(${displayBannerSrc})`, backgroundSize: 'cover', backgroundPosition: 'center' } - : { background: bannerFallback, opacity: 0.5 } + : { background: mutedGradient( + effectiveAccent ?? getAvatarGradient(user.homeUserId ?? user.id, effectiveDisplayName, effectiveAvatarColor).from, + effectiveAccent ? adjustColor(effectiveAccent, -40) : getAvatarGradient(user.homeUserId ?? user.id, effectiveDisplayName, effectiveAvatarColor).to, + 0.5 + ) } } />
diff --git a/packages/web/src/components/ui/UserProfilePopout.tsx b/packages/web/src/components/ui/UserProfilePopout.tsx index 61fe3cb3..6e913acd 100644 --- a/packages/web/src/components/ui/UserProfilePopout.tsx +++ b/packages/web/src/components/ui/UserProfilePopout.tsx @@ -6,7 +6,7 @@ import { Avatar } from '../ui/Avatar'; import { Username } from '../ui/Username'; import { useSpaceStore, getApiForOrigin, resolveUserOrigin } from '../../stores/spaceStore'; import { useUIStore } from '../../stores/uiStore'; -import { getAvatarGradient, adjustColor } from '../../utils/gradients'; +import { getAvatarGradient, adjustColor, mutedGradient } from '../../utils/gradients'; import { parseFederatedUsername } from '../../utils/identity'; import { loadFederatedMutuals } from '../../utils/mutuals'; @@ -71,8 +71,11 @@ export function UserProfilePopout({ user, onClose, position }: UserProfilePopout ? (user.banner.startsWith('http') ? user.banner : userApi.uploads.url(user.banner)) : null; const bannerFallback = user.accentColor - ? `linear-gradient(135deg, ${user.accentColor}, ${adjustColor(user.accentColor, -40)})` - : getAvatarGradient(user.homeUserId ?? user.id, displayName, user.avatarColor).gradient; + ? mutedGradient(user.accentColor, adjustColor(user.accentColor, -40)) + : (() => { + const g = getAvatarGradient(user.homeUserId ?? user.id, displayName, user.avatarColor); + return mutedGradient(g.from, g.to); + })(); return (
diff --git a/packages/web/src/utils/gradients.ts b/packages/web/src/utils/gradients.ts index b076501b..3a4e2603 100644 --- a/packages/web/src/utils/gradients.ts +++ b/packages/web/src/utils/gradients.ts @@ -89,6 +89,19 @@ export const BANNER_COLOR_PRESETS: string[][] = [ /** @deprecated Use BANNER_COLOR_PRESETS instead. */ export const ACCENT_PRESETS: string[] = BANNER_COLOR_PRESETS.map(f => f[1]!); +/** Convert 6-digit hex (#rrggbb) to rgba string. */ +export function hexToRgba(hex: string, alpha: number): string { + const r = parseInt(hex.slice(1, 3), 16); + const g = parseInt(hex.slice(3, 5), 16); + const b = parseInt(hex.slice(5, 7), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +/** Build a banner-style muted gradient from two hex colors with baked-in alpha. */ +export function mutedGradient(from: string, to: string, alpha = 0.6): string { + return `linear-gradient(135deg, ${hexToRgba(from, alpha)}, ${hexToRgba(to, alpha)})`; +} + /** Shift each RGB component of a hex color by `amount` (positive = lighter, negative = darker). */ export function adjustColor(hex: string, amount: number): string { const r = Math.max(0, Math.min(255, parseInt(hex.slice(1, 3), 16) + amount));