fix: rewrite decoration grid to contain images within cells
CI / Build & Quality Checks (push) Successful in 11m39s
Trigger Desktop Build / trigger (push) Successful in 6s

The INSET overflow approach (position:absolute images extending beyond
52×52 buttons) was fundamentally broken: absolutely positioned children
don't contribute to flex row height, so rowGap controlled button-to-button
spacing but image pixels still painted into the gap, causing visual overlap
regardless of how large rowGap was set.

New approach: 72×72 circle cells, overflow:hidden, image fills the cell
via inset:0 with objectFit:contain. Gap of 16px is actual clear space
between cell edges — no math needed. Also bumped maxHeight 420→480.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 14:49:42 -04:00
parent d7d7b59866
commit a5fe358313
@@ -12,7 +12,7 @@ import {
import { invalidateDecorationCache } from '../../../hooks/useAvatarDecoration';
const PROFILE_FIELD = 'io.lotus.avatar_decoration';
const INSET = 8;
const CELL_SIZE = 72;
function DecorationPreviewCell({
slug,
@@ -34,29 +34,19 @@ function DecorationPreviewCell({
onClick={() => onSelect(slug)}
style={{
position: 'relative',
width: 52,
height: 52,
width: CELL_SIZE,
height: CELL_SIZE,
flexShrink: 0,
border: `2px solid ${selected ? 'var(--accent-cyan)' : 'transparent'}`,
borderRadius: '0.75rem',
borderRadius: '50%',
background: 'var(--bg-surface-variant)',
cursor: 'pointer',
padding: 0,
boxShadow: selected ? '0 0 0 1px var(--accent-cyan)' : 'none',
overflow: 'visible',
overflow: 'hidden',
outline: 'none',
}}
>
{/* Avatar placeholder tint */}
<div
style={{
position: 'absolute',
inset: 0,
borderRadius: '0.75rem',
background: 'var(--bg-surface-variant)',
overflow: 'hidden',
}}
/>
<img
src={`${DECORATION_CDN}/${slug}.png`}
alt={name}
@@ -64,10 +54,9 @@ function DecorationPreviewCell({
decoding="async"
style={{
position: 'absolute',
top: -INSET,
left: -INSET,
width: `calc(100% + ${INSET * 2}px)`,
height: `calc(100% + ${INSET * 2}px)`,
inset: 0,
width: '100%',
height: '100%',
objectFit: 'contain',
pointerEvents: 'none',
}}
@@ -149,12 +138,12 @@ export function ProfileDecoration() {
<div
style={{
position: 'relative',
width: 52,
height: 52,
width: CELL_SIZE,
height: CELL_SIZE,
flexShrink: 0,
borderRadius: '0.75rem',
borderRadius: '50%',
background: 'var(--bg-surface-variant)',
overflow: 'visible',
overflow: 'hidden',
}}
>
{selected && (
@@ -163,10 +152,9 @@ export function ProfileDecoration() {
alt="Selected decoration preview"
style={{
position: 'absolute',
top: -INSET,
left: -INSET,
width: `calc(100% + ${INSET * 2}px)`,
height: `calc(100% + ${INSET * 2}px)`,
inset: 0,
width: '100%',
height: '100%',
objectFit: 'contain',
pointerEvents: 'none',
}}
@@ -236,7 +224,7 @@ export function ProfileDecoration() {
direction="Column"
gap="300"
style={{
maxHeight: 420,
maxHeight: 480,
overflowY: 'auto',
paddingRight: 4,
}}
@@ -250,12 +238,8 @@ export function ProfileDecoration() {
style={{
display: 'flex',
flexWrap: 'wrap',
columnGap: 28,
rowGap: 52,
paddingBottom: INSET,
paddingLeft: INSET,
paddingRight: INSET,
paddingTop: INSET,
gap: 16,
padding: 4,
}}
>
{category.decorations.map((d) => (