diff --git a/src/components/common/gift/SavedGift.tsx b/src/components/common/gift/SavedGift.tsx
index 7ff16a23b..bad9253d7 100644
--- a/src/components/common/gift/SavedGift.tsx
+++ b/src/components/common/gift/SavedGift.tsx
@@ -140,6 +140,9 @@ const SavedGift = ({
backgroundColors={backdropColors}
patternColor={patternColor}
patternIcon={pattern.sticker}
+ patternSize={14}
+ ringsCount={1}
+ ovalFactor={1}
/>
);
}, [backdrop, pattern]);
diff --git a/src/components/common/profile/ProfileInfo.module.scss b/src/components/common/profile/ProfileInfo.module.scss
index 5fe1d3705..e4a9c369b 100644
--- a/src/components/common/profile/ProfileInfo.module.scss
+++ b/src/components/common/profile/ProfileInfo.module.scss
@@ -386,14 +386,9 @@
.radialPatternBackground {
pointer-events: none;
-
- // Hacky way to keep background stable during resizes
position: absolute;
- top: 8rem;
-
+ inset: 0;
aspect-ratio: 1 / 1;
- width: 140%;
- margin-top: -70%;
}
.standaloneAvatar {
diff --git a/src/components/common/profile/ProfileInfo.tsx b/src/components/common/profile/ProfileInfo.tsx
index f74f10a9e..bac0d0c30 100644
--- a/src/components/common/profile/ProfileInfo.tsx
+++ b/src/components/common/profile/ProfileInfo.tsx
@@ -45,6 +45,7 @@ import buildStyle from '../../../util/buildStyle';
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { resolveTransitionName } from '../../../util/resolveTransitionName';
+import { REM } from '../helpers/mediaDimensions.ts';
import renderText from '../helpers/renderText.tsx';
import { useVtn } from '../../../hooks/animations/useVtn';
@@ -104,9 +105,7 @@ const EMOJI_TOPIC_SIZE = 120;
const LOAD_MORE_THRESHOLD = 3;
const MAX_PHOTO_DASH_COUNT = 30;
const STATUS_UPDATE_INTERVAL = 1000 * 60; // 1 min
-const PATTERN_COLOR = '#000000';
-const PATTERN_SIZE_FACTOR = 0.75;
-const PATTERN_OPACITY = 0.75;
+const PATTERN_Y_SHIFT = 8 * REM;
const ProfileInfo = ({
isExpanded,
@@ -493,10 +492,11 @@ const ProfileInfo = ({
)}
{pinnedGifts && (
diff --git a/src/components/common/profile/RadialPatternBackground.module.scss b/src/components/common/profile/RadialPatternBackground.module.scss
index 9c430468a..67a9cd379 100644
--- a/src/components/common/profile/RadialPatternBackground.module.scss
+++ b/src/components/common/profile/RadialPatternBackground.module.scss
@@ -1,4 +1,6 @@
.root {
+ --_y-shift: 50%;
+
overflow: hidden;
border-radius: inherit;
@@ -9,9 +11,15 @@
position: absolute;
inset: 0;
- background-image:
- radial-gradient(circle closest-side, #ffffff32 3rem, #ffffff00 7rem),
- radial-gradient(closest-side, var(--_bg-1), var(--_bg-2));
+ background-image: radial-gradient(closest-side at 50% var(--_y-shift), var(--_bg-radial-1) 25%, var(--_bg-radial-2) 125%);
+ }
+
+ &.withLinearGradient {
+ &::before {
+ background-image:
+ radial-gradient(closest-side at 50% var(--_y-shift), var(--_bg-radial-1) 25%, var(--_bg-radial-2) 125%),
+ linear-gradient(var(--_bg-linear-1), var(--_bg-linear-2));
+ }
}
}
diff --git a/src/components/common/profile/RadialPatternBackground.tsx b/src/components/common/profile/RadialPatternBackground.tsx
index 4ba99b468..4ddd658d3 100644
--- a/src/components/common/profile/RadialPatternBackground.tsx
+++ b/src/components/common/profile/RadialPatternBackground.tsx
@@ -1,6 +1,4 @@
-import {
- memo, useEffect, useMemo, useRef, useSignal, useState,
-} from '../../../lib/teact/teact';
+import { memo, useEffect, useLayoutEffect, useMemo, useRef, useSignal, useState } from '../../../lib/teact/teact';
import type { ApiSticker } from '../../../api/types';
@@ -9,7 +7,8 @@ import { getStickerMediaHash } from '../../../global/helpers';
import buildClassName from '../../../util/buildClassName';
import buildStyle from '../../../util/buildStyle';
import { preloadImage } from '../../../util/files';
-import { clamp } from '../../../util/math';
+import { hexToRgb } from '../../../util/switchTheme.ts';
+import { REM } from '../helpers/mediaDimensions';
import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
@@ -20,31 +19,39 @@ import styles from './RadialPatternBackground.module.scss';
type OwnProps = {
backgroundColors: string[];
- patternColor?: string;
patternIcon?: ApiSticker;
+ patternColor?: string;
+ patternSize?: number;
+ ringsCount?: number;
+ ovalFactor?: number;
+ withLinearGradient?: boolean;
className?: string;
clearBottomSector?: boolean;
- patternSize?: number;
- patternOpacity?: number;
+ yPosition?: number;
};
-const RINGS = 3;
const BASE_RING_ITEM_COUNT = 8;
const RING_INCREMENT = 0.5;
-const CENTER_EMPTINESS = 0.05;
-const MAX_RADIUS = 0.4;
-const BASE_ICON_SIZE = 20;
+const CENTER_EMPTINESS = 0.1;
+const MAX_RADIUS = 0.43;
+const MIN_SIZE = 4 * REM;
+const PATTERN_OPACITY = 0.9;
-const MIN_SIZE = 250;
+const DEFAULT_PATTERN_SIZE = 20;
+const DEFAULT_RINGS_COUNT = 3;
+const DEFAULT_OVAL_FACTOR = 1.4;
const RadialPatternBackground = ({
backgroundColors,
- patternColor,
patternIcon,
- patternOpacity,
+ patternColor,
+ patternSize = DEFAULT_PATTERN_SIZE,
+ ringsCount = DEFAULT_RINGS_COUNT,
+ ovalFactor = DEFAULT_OVAL_FACTOR,
+ withLinearGradient,
clearBottomSector,
className,
- patternSize = 1,
+ yPosition,
}: OwnProps) => {
const containerRef = useRef();
const canvasRef = useRef();
@@ -65,11 +72,10 @@ const RadialPatternBackground = ({
const patternPositions = useMemo(() => {
const coordinates: { x: number; y: number; sizeFactor: number }[] = [];
- for (let ring = 1; ring <= RINGS; ring++) {
+ for (let ring = 1; ring <= ringsCount; ring++) {
const ringItemCount = Math.floor(BASE_RING_ITEM_COUNT * (1 + (ring - 1) * RING_INCREMENT));
- const ringProgress = ring / RINGS;
+ const ringProgress = ring / ringsCount;
const ringRadius = CENTER_EMPTINESS + (MAX_RADIUS - CENTER_EMPTINESS) * ringProgress;
-
const angleShift = ring % 2 === 0 ? Math.PI / ringItemCount : 0;
for (let i = 0; i < ringItemCount; i++) {
@@ -79,11 +85,9 @@ const RadialPatternBackground = ({
continue;
}
- // Slightly oval
- const xOffset = ringRadius * 1.71 * Math.cos(angle);
+ const xOffset = ringRadius * Math.cos(angle) * ovalFactor;
const yOffset = ringRadius * Math.sin(angle);
-
- const sizeFactor = 1.4 - ringProgress * Math.random();
+ const sizeFactor = 1.65 - ringProgress + Math.random() / ringsCount;
coordinates.push({
x: xOffset,
@@ -93,7 +97,7 @@ const RadialPatternBackground = ({
}
}
return coordinates;
- }, [clearBottomSector]);
+ }, [clearBottomSector, ovalFactor, ringsCount]);
useResizeObserver(containerRef, (entry) => {
setContainerSize({
@@ -119,59 +123,67 @@ const RadialPatternBackground = ({
const { width, height } = canvas;
if (!width || !height) return;
+ const centerX = width / 2;
+ const centerY = yPosition !== undefined ? yPosition * dpr : height / 2;
+
ctx.clearRect(0, 0, width, height);
- ctx.save();
- patternPositions.forEach(({
- x, y, sizeFactor,
- }) => {
- const renderX = x * patternSize * Math.max(width, MIN_SIZE * dpr) + width / 2;
- const renderY = y * patternSize * Math.max(height, MIN_SIZE * dpr) + height / 2;
+ const patternImage = drawPattern(
+ width, height, centerX, centerY, backgroundColors[1] ?? backgroundColors[0],
+ );
+ ctx.drawImage(patternImage, 0, 0, width, height);
- const size = BASE_ICON_SIZE * dpr * patternSize * sizeFactor;
-
- ctx.drawImage(emojiImage, renderX - size / 2, renderY - size / 2, size, size);
- });
- ctx.restore();
-
- if (patternColor) {
- ctx.save();
- ctx.fillStyle = patternColor;
- ctx.globalCompositeOperation = 'source-atop';
- ctx.fillRect(0, 0, width, height);
- ctx.restore();
- }
-
- const radialGradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, width / 2);
-
- const alpha = clamp(0.6 * (patternOpacity ?? 1), 0, 1);
-
- radialGradient.addColorStop(0, `rgb(255 255 255 / ${1 - alpha})`);
- radialGradient.addColorStop(1, `rgb(255 255 255 / 1)`);
+ const radialGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, width / 2);
+ radialGradient.addColorStop(0.75, 'rgb(255 255 255 / 0)');
+ radialGradient.addColorStop(1, 'rgb(255 255 255 / 0.75)');
// Alpha mask
- ctx.save();
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = radialGradient;
ctx.fillRect(0, 0, width, height);
- ctx.restore();
+ });
+
+ const drawPattern = useLastCallback((
+ width: number, height: number, centerX: number, centerY: number, color: string,
+ ) => {
+ const canvas = new OffscreenCanvas(width, height);
+ const ctx = canvas.getContext('2d')!;
+
+ ctx.globalAlpha = PATTERN_OPACITY;
+
+ patternPositions.forEach(({
+ x, y, sizeFactor,
+ }) => {
+ const renderX = x * Math.max(width, MIN_SIZE * dpr) + centerX;
+ const renderY = yPosition !== undefined ? y * Math.max(width, MIN_SIZE * dpr) + centerY
+ : y * Math.max(height, MIN_SIZE * dpr) + centerY;
+
+ const size = patternSize * dpr * sizeFactor;
+
+ ctx.drawImage(emojiImage!, renderX - size / 2, renderY - size / 2, size, size);
+ });
+
+ ctx.fillStyle = adjustBrightness(color, -0.075);
+ ctx.globalCompositeOperation = 'source-in';
+ ctx.fillRect(0, 0, width, height);
+
+ return canvas;
});
useEffect(() => {
draw();
- }, [emojiImage, patternOpacity, patternSize, patternColor, patternPositions]);
+ }, [emojiImage, patternColor, patternPositions, yPosition]);
- useEffect(() => {
+ useLayoutEffect(() => {
const { width, height } = getContainerSize();
const canvas = canvasRef.current;
if (!width || !height || !canvas) {
return;
}
- const maxSide = Math.max(width, height);
requestMutation(() => {
- canvas.width = maxSide * dpr;
- canvas.height = maxSide * dpr;
+ canvas.width = width * dpr;
+ canvas.height = height * dpr;
draw();
});
@@ -180,10 +192,10 @@ const RadialPatternBackground = ({
return (