Files
cinny/src/app/features/settings/account/CollectiblesSection.css.ts
litruv 0cbc5d9a1c
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 1s
Add Discord collectibles to profiles, messages, and settings.
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.
2026-08-24 00:54:48 +10:00

333 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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',
});