Add Discord collectibles to profiles, messages, and settings.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
Integrate profile effects, nameplates, and avatar decorations with live catalog browsing, Matrix profile storage, and rendering across user heroes, DMs, messages, and the sidebar avatar.
This commit is contained in:
332
src/app/features/settings/account/CollectiblesSection.css.ts
Normal file
332
src/app/features/settings/account/CollectiblesSection.css.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
import { style } from '@vanilla-extract/css';
|
||||
import { color, toRem } from 'folds';
|
||||
|
||||
/** Discord nameplate static.png assets are 448×84. */
|
||||
export const NAMEPLATE_ASPECT_RATIO = '448 / 84';
|
||||
|
||||
/**
|
||||
* Mini profile hero preview — matches UserRoomProfile menu width (260) and UserHero layout,
|
||||
* with extra vertical space below the name so effects have room to play.
|
||||
*/
|
||||
export const PROFILE_HERO_PREVIEW_WIDTH = 260;
|
||||
export const PROFILE_HERO_CONTENT_HEIGHT = 235;
|
||||
export const PROFILE_HERO_PREVIEW_EXTRA_HEIGHT = Math.round(PROFILE_HERO_CONTENT_HEIGHT * 0.5);
|
||||
export const PROFILE_HERO_PREVIEW_HEIGHT = PROFILE_HERO_CONTENT_HEIGHT + PROFILE_HERO_PREVIEW_EXTRA_HEIGHT;
|
||||
export const PROFILE_HERO_PREVIEW_ASPECT_RATIO = `${PROFILE_HERO_PREVIEW_WIDTH} / ${PROFILE_HERO_PREVIEW_HEIGHT}`;
|
||||
|
||||
export const CollectibleGroupGrid = style({
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const CollectibleGroupGridNameplate = style({
|
||||
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
||||
gap: toRem(3),
|
||||
});
|
||||
|
||||
export const CollectibleGroupGridEffects = style({
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(130px, 1fr))',
|
||||
gap: toRem(12),
|
||||
});
|
||||
|
||||
export const CollectibleGroupGridDecorations = style({
|
||||
gridTemplateColumns: 'repeat(auto-fill, minmax(120px, 1fr))',
|
||||
gap: toRem(12),
|
||||
});
|
||||
|
||||
export const CollectibleGroupCard = style({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: toRem(4),
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const CollectibleGroupPreview = style({
|
||||
position: 'relative',
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
padding: 0,
|
||||
overflow: 'visible',
|
||||
background: 'transparent',
|
||||
cursor: 'pointer',
|
||||
textAlign: 'left',
|
||||
});
|
||||
|
||||
export const CollectibleGroupPreviewDisabled = style({
|
||||
cursor: 'wait',
|
||||
});
|
||||
|
||||
export const CollectibleGroupPreviewDimmed = style({
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
export const CollectiblePreviewFrame = style({
|
||||
width: '100%',
|
||||
borderRadius: toRem(6),
|
||||
overflow: 'hidden',
|
||||
backgroundColor: color.Surface.ContainerLine,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
});
|
||||
|
||||
export const CollectiblePreviewFrameDecoration = style({
|
||||
overflow: 'visible',
|
||||
backgroundColor: 'transparent',
|
||||
});
|
||||
|
||||
export const CollectiblePreviewFrameProfileEffect = style({
|
||||
backgroundColor: color.Surface.Container,
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'stretch',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroPreview = style({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: color.Surface.Container,
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroCover = style({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
height: `${(140 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
overflow: 'hidden',
|
||||
zIndex: 1,
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroBanner = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroCoverBlur = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
filter: 'blur(16px)',
|
||||
transform: 'scale(2)',
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroAvatarSlot = style({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: `${(140 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
height: `${(29 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
zIndex: 4,
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroAvatar = style({
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: 0,
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: `${(72 / PROFILE_HERO_PREVIEW_WIDTH) * 100}%`,
|
||||
aspectRatio: '1',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
border: `${toRem(1)} solid ${color.Surface.Container}`,
|
||||
backgroundColor: color.Surface.Container,
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroAvatarImg = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'block',
|
||||
objectFit: 'cover',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroInfo = style({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: `${((140 + 29) / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
marginTop: `${(-36 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
paddingTop: `${(44 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
paddingLeft: `${(16 / PROFILE_HERO_PREVIEW_WIDTH) * 100}%`,
|
||||
paddingRight: `${(16 / PROFILE_HERO_PREVIEW_WIDTH) * 100}%`,
|
||||
paddingBottom: `${(16 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: `${(8 / PROFILE_HERO_PREVIEW_HEIGHT) * 100}%`,
|
||||
textAlign: 'center',
|
||||
zIndex: 4,
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroName = style({
|
||||
width: '100%',
|
||||
fontSize: toRem(11),
|
||||
lineHeight: 1.2,
|
||||
fontWeight: 600,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroEffectLayer = style({
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
zIndex: 10,
|
||||
pointerEvents: 'none',
|
||||
overflow: 'visible',
|
||||
});
|
||||
|
||||
export const ProfileEffectHeroEffect = style({
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
width: '100%',
|
||||
aspectRatio: '450 / 880',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'top center',
|
||||
});
|
||||
|
||||
export const NameplatePreview = style({
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
aspectRatio: NAMEPLATE_ASPECT_RATIO,
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'center',
|
||||
borderRadius: toRem(4),
|
||||
});
|
||||
|
||||
export const NameplatePreviewFallback = style({
|
||||
width: '100%',
|
||||
aspectRatio: NAMEPLATE_ASPECT_RATIO,
|
||||
borderRadius: toRem(4),
|
||||
});
|
||||
|
||||
export const CollectibleHoverCaption = style({
|
||||
position: 'absolute',
|
||||
top: `calc(100% + ${toRem(2)})`,
|
||||
left: 0,
|
||||
right: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: toRem(1),
|
||||
padding: `${toRem(4)} ${toRem(6)}`,
|
||||
background: color.Surface.Container,
|
||||
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
|
||||
borderRadius: toRem(4),
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.12)',
|
||||
opacity: 0,
|
||||
transition: 'opacity 0.15s ease',
|
||||
pointerEvents: 'none',
|
||||
zIndex: 2,
|
||||
selectors: {
|
||||
[`${CollectibleGroupPreview}:hover &`]: {
|
||||
opacity: 1,
|
||||
},
|
||||
[`${CollectibleGroupPreview}:focus-visible &`]: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const CollectibleHoverLabel = style({
|
||||
color: color.Surface.OnContainer,
|
||||
fontSize: toRem(12),
|
||||
fontWeight: 500,
|
||||
lineHeight: 1.3,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const CollectibleHoverMeta = style({
|
||||
color: color.Surface.OnContainer,
|
||||
fontSize: toRem(10),
|
||||
lineHeight: 1.3,
|
||||
opacity: 0.7,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
});
|
||||
|
||||
export const VariantRow = style({
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
gap: toRem(4),
|
||||
justifyContent: 'center',
|
||||
padding: `0 ${toRem(2)}`,
|
||||
});
|
||||
|
||||
export const VariantChip = style({
|
||||
border: `${toRem(1)} solid ${color.Surface.ContainerLine}`,
|
||||
borderRadius: toRem(4),
|
||||
padding: 0,
|
||||
background: color.Surface.Container,
|
||||
cursor: 'pointer',
|
||||
overflow: 'hidden',
|
||||
flexShrink: 0,
|
||||
});
|
||||
|
||||
export const VariantChipSelected = style({
|
||||
border: `${toRem(2)} solid ${color.Primary.Main}`,
|
||||
});
|
||||
|
||||
export const VariantChipNameplate = style({
|
||||
width: toRem(28),
|
||||
height: toRem(10),
|
||||
borderRadius: toRem(3),
|
||||
});
|
||||
|
||||
export const VariantChipThumbnail = style({
|
||||
width: toRem(28),
|
||||
height: toRem(28),
|
||||
objectFit: 'cover',
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
export const VariantChipDecorationStack = style({
|
||||
position: 'relative',
|
||||
width: toRem(28),
|
||||
height: toRem(28),
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
export const VariantChipDecorationAvatar = style({
|
||||
width: '62.5%',
|
||||
height: '62.5%',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
});
|
||||
|
||||
export const DecorationPreviewAvatar = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'block',
|
||||
objectFit: 'cover',
|
||||
});
|
||||
|
||||
export const GridPreviewMedia = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
objectPosition: 'top center',
|
||||
});
|
||||
|
||||
export const GridPreviewMediaCover = style({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
objectPosition: 'top center',
|
||||
});
|
||||
Reference in New Issue
Block a user