Improve Blobbie Appearance (#4173)

Blobbies now select from a subset of predefined colors to form a basic radial gradient form the bottom right corner.

![image](https://github.com/user-attachments/assets/5b708533-506d-417d-ac49-2356aa0a2247)

<!-- start pr-codex -->

---

## PR-Codex overview
This PR updates the appearance of the Blobbie avatar in the ConnectWallet feature of `thirdweb`.

### Detailed summary
- Updated Blobbie appearance with unique gradient colors based on address
- Replaced complex color pairing logic with predefined color options
- Simplified Blobbie rendering logic for better performance

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
This commit is contained in:
gregfromstl
2024-08-19 06:17:43 +00:00
parent 574ff0295d
commit 7778063458
3 changed files with 39 additions and 118 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"thirdweb": patch
---
Update Blobbie appearance
@@ -1,17 +1,25 @@
"use client";
import { hexToNumber } from "@noble/curves/abstract/utils";
import { useId, useMemo } from "react";
import { type Address, numberToHex } from "viem";
import type { Address } from "viem";
// Distance between 2 colors (in RGB)
type Color = [number, number, number];
function hexToRgb(hex: string) {
return [
Number(hexToNumber(hex.slice(0, 2))),
Number(hexToNumber(hex.slice(2, 4))),
Number(hexToNumber(hex.slice(4, 6))),
] satisfies [number, number, number];
}
const COLOR_OPTIONS = [
["#fca5a5", "#b91c1c"],
["#fdba74", "#c2410c"],
["#fcd34d", "#b45309"],
["#fde047", "#a16207"],
["#a3e635", "#4d7c0f"],
["#86efac", "#15803d"],
["#67e8f9", "#0e7490"],
["#7dd3fc", "#0369a1"],
["#93c5fd", "#1d4ed8"],
["#a5b4fc", "#4338ca"],
["#c4b5fd", "#6d28d9"],
["#d8b4fe", "#7e22ce"],
["#f0abfc", "#a21caf"],
["#f9a8d4", "#be185d"],
["#fda4af", "#be123c"],
];
/**
* A unique gradient avatar based on the provided address.
@@ -21,114 +29,22 @@ function hexToRgb(hex: string) {
*/
export function Blobbie(props: { address: Address; size: number }) {
const id = useId();
const colors: [string, string, string] = useMemo(() => {
const color = props.address.slice(2, 8);
const rgb = hexToRgb(color);
// To get well-paired colors, we use the first rgb hex sequence as our main color then find its two best pairs (split color wheel into thirds)
// To prevent extremely dark colors, which tend to clash, we don't allow values less than 55
const pairing1 = rgb.map((n) => (n + 85 > 255 ? n + 85 - 200 : n + 85));
const pairing2 = rgb.map((n) => (n - 85 < 55 ? n - 85 + 200 : n - 85));
return [
color,
pairing1.map((n) => numberToHex(n).replace("0x", "")).join(""),
pairing2.map((n) => numberToHex(n).replace("0x", "")).join(""),
];
}, [props.address]);
const positions: [Color, Color, Color, Color, Color] = useMemo(() => {
const _positions: Color[] = [];
let i = 8;
while (i < 44) {
const values = hexToRgb(props.address.slice(i, i + 6));
_positions.push(values);
i += 6;
}
return _positions as [Color, Color, Color, Color, Color];
}, [props.address]);
console.log(colors);
const colors = useMemo(
() =>
COLOR_OPTIONS[
Number(hexToNumber(props.address.slice(2, 4))) % COLOR_OPTIONS.length
] as [string, string],
[props.address],
);
return (
<svg
height={`${props.size}px`}
style={{ background: "#FFFFFF" }}
width={`${props.size}px`}
role="presentation"
viewBox="0 0 500 500"
xmlns="http://www.w3.org/2000/svg"
>
<defs>
{colors.map((color, idx) => {
return (
<radialGradient
id={`${id}_grad${idx + 1}`}
cx="50%"
cy="50%"
r="50%"
key={`grad${
// biome-ignore lint/suspicious/noArrayIndexKey: Jonas said so
idx
}`}
>
<stop
offset="0%"
style={{ stopColor: `#${color}`, stopOpacity: 0.9 }}
/>
<stop
offset="100%"
style={{ stopColor: `#${color}`, stopOpacity: 0 }}
/>
</radialGradient>
);
})}
</defs>
<ellipse
cx="250"
cy="250"
rx="500"
ry="500"
fill={`url(#${id}_grad1)`}
opacity={0.5}
/>
<ellipse
cx={400 + positions[0][0]}
cy={400 + positions[0][1]}
rx={350 + positions[0][2]}
ry={350 + positions[0][2]}
fill={`url(#${id}_grad1)`}
/>
<ellipse
cx={positions[1][0]}
cy={positions[1][1]}
rx={350 + positions[1][2]}
ry={350 + positions[1][2]}
fill={`url(#${id}_grad2)`}
/>
<ellipse
cx={400 + positions[2][0]}
cy={positions[2][1]}
rx={350 + positions[2][2]}
ry={350 + positions[2][2]}
fill={`url(#${id}_grad3)`}
/>
<ellipse
cx={positions[3][0]}
cy={positions[3][1]}
rx={100 + positions[3][2]}
ry={100 + positions[3][2]}
fill={`url(#${id}_grad2)`}
opacity={0.5}
/>
<ellipse
cx={positions[4][0]}
cy={positions[4][1]}
rx={100 + positions[4][2]}
ry={100 + positions[4][2]}
fill={`url(#${id}_grad3)`}
opacity={0.5}
/>
</svg>
<div
id={id}
style={{
width: `${props.size}px`,
height: `${props.size}px`,
backgroundImage: `radial-gradient(ellipse at left bottom, ${colors[0]}, ${colors[1]})`,
}}
/>
);
}
@@ -213,7 +213,7 @@ export const ConnectedWalletDetails: React.FC<{
style={{
border: `1px solid ${theme.colors.borderColor}`,
textOverflow: "ellipsis",
width: "115px",
minWidth: "115px",
height: "50px",
whiteSpace: "nowrap",
borderRadius: `0 ${radius.md} ${radius.md} 0`,