@ -84,6 +84,7 @@
|
|||||||
"emoji-datasource": "15.0.1",
|
"emoji-datasource": "15.0.1",
|
||||||
"emoji-mart": "^5.6.0",
|
"emoji-mart": "^5.6.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
|
"fast-average-color": "^9.5.0",
|
||||||
"fasttext.wasm.js": "^1.0.0",
|
"fasttext.wasm.js": "^1.0.0",
|
||||||
"flexsearch": "^0.7.43",
|
"flexsearch": "^0.7.43",
|
||||||
"fuzzysort": "^3.1.0",
|
"fuzzysort": "^3.1.0",
|
||||||
|
|||||||
@ -193,7 +193,7 @@ const Account = ({
|
|||||||
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
||||||
<HStack alignItems='center' space={3} className='overflow-hidden'>
|
<HStack alignItems='center' space={3} className='overflow-hidden'>
|
||||||
<div className='rounded-lg'>
|
<div className='rounded-lg'>
|
||||||
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} />
|
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} isCat={account.is_cat} />
|
||||||
{emoji && (
|
{emoji && (
|
||||||
<Emoji
|
<Emoji
|
||||||
className='!absolute -right-1.5 bottom-0 size-5'
|
className='!absolute -right-1.5 bottom-0 size-5'
|
||||||
@ -242,14 +242,14 @@ const Account = ({
|
|||||||
return (
|
return (
|
||||||
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
|
<div data-testid='account' className='group block w-full shrink-0' ref={overflowRef}>
|
||||||
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
<HStack alignItems={actionAlignment} space={3} justifyContent='between'>
|
||||||
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3} className='overflow-hidden'>
|
<HStack alignItems={withAccountNote || note ? 'top' : 'center'} space={3}>
|
||||||
{withAvatar && (
|
{withAvatar && (
|
||||||
<ProfilePopper
|
<ProfilePopper
|
||||||
condition={showAccountHoverCard}
|
condition={showAccountHoverCard}
|
||||||
wrapper={(children) => <HoverAccountWrapper className='relative' accountId={account.id} element='span'>{children}</HoverAccountWrapper>}
|
wrapper={(children) => <HoverAccountWrapper className='relative' accountId={account.id} element='span'>{children}</HoverAccountWrapper>}
|
||||||
>
|
>
|
||||||
<LinkEl className='rounded-lg' {...linkProps}>
|
<LinkEl className='rounded-lg' {...linkProps}>
|
||||||
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} />
|
<Avatar src={account.avatar} size={avatarSize} alt={account.avatar_description} isCat={account.is_cat} />
|
||||||
{emoji && (
|
{emoji && (
|
||||||
<Emoji
|
<Emoji
|
||||||
className='!absolute -right-1.5 bottom-0 size-5'
|
className='!absolute -right-1.5 bottom-0 size-5'
|
||||||
|
|||||||
@ -27,6 +27,7 @@ const AvatarStack: React.FC<IAvatarStack> = ({ accountIds, limit = 3 }) => {
|
|||||||
src={account.avatar}
|
src={account.avatar}
|
||||||
alt={account.avatar_description}
|
alt={account.avatar_description}
|
||||||
size={20}
|
size={20}
|
||||||
|
isCat={account.is_cat}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ interface IStillImage {
|
|||||||
alt?: string;
|
alt?: string;
|
||||||
/** Extra class names for the outer <div> container. */
|
/** Extra class names for the outer <div> container. */
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** Extra class names for the inner <img> element. */
|
||||||
|
innerClassName?: string;
|
||||||
/** URL to the image */
|
/** URL to the image */
|
||||||
src: string;
|
src: string;
|
||||||
/** Extra CSS styles on the outer <div> element. */
|
/** Extra CSS styles on the outer <div> element. */
|
||||||
@ -18,6 +20,8 @@ interface IStillImage {
|
|||||||
showExt?: boolean;
|
showExt?: boolean;
|
||||||
/** Callback function if the image fails to load */
|
/** Callback function if the image fails to load */
|
||||||
onError?(): void;
|
onError?(): void;
|
||||||
|
/** Callback function if the image loads successfully */
|
||||||
|
onLoad?: React.ReactEventHandler<HTMLImageElement>;
|
||||||
/** Treat as animated, no matter the extension */
|
/** Treat as animated, no matter the extension */
|
||||||
isGif?: boolean;
|
isGif?: boolean;
|
||||||
/** Specify that the group is defined by the parent */
|
/** Specify that the group is defined by the parent */
|
||||||
@ -26,7 +30,7 @@ interface IStillImage {
|
|||||||
|
|
||||||
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
|
/** Renders images on a canvas, only playing GIFs if autoPlayGif is enabled. */
|
||||||
const StillImage: React.FC<IStillImage> = ({
|
const StillImage: React.FC<IStillImage> = ({
|
||||||
alt, className, src, style, letterboxed = false, showExt = false, onError, isGif, noGroup,
|
alt, className, innerClassName, src, style, letterboxed = false, showExt = false, onError, onLoad, isGif, noGroup,
|
||||||
}) => {
|
}) => {
|
||||||
const { autoPlayGif } = useSettings();
|
const { autoPlayGif } = useSettings();
|
||||||
|
|
||||||
@ -37,16 +41,20 @@ const StillImage: React.FC<IStillImage> = ({
|
|||||||
src && !autoPlayGif && ((isGif) || src.endsWith('.gif') || src.startsWith('blob:'))
|
src && !autoPlayGif && ((isGif) || src.endsWith('.gif') || src.startsWith('blob:'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleImageLoad = () => {
|
const handleImageLoad: React.ReactEventHandler<HTMLImageElement> = (e) => {
|
||||||
if (hoverToPlay && canvas.current && img.current) {
|
if (hoverToPlay && canvas.current && img.current) {
|
||||||
canvas.current.width = img.current.naturalWidth;
|
canvas.current.width = img.current.naturalWidth;
|
||||||
canvas.current.height = img.current.naturalHeight;
|
canvas.current.height = img.current.naturalHeight;
|
||||||
canvas.current.getContext('2d')?.drawImage(img.current, 0, 0);
|
canvas.current.getContext('2d')?.drawImage(img.current, 0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (onLoad) {
|
||||||
|
onLoad(e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/** ClassNames shared between the `<img>` and `<canvas>` elements. */
|
/** ClassNames shared between the `<img>` and `<canvas>` elements. */
|
||||||
const baseClassName = clsx('block size-full', {
|
const baseClassName = clsx('block size-full', innerClassName, {
|
||||||
'object-contain': letterboxed,
|
'object-contain': letterboxed,
|
||||||
'object-cover': !letterboxed,
|
'object-cover': !letterboxed,
|
||||||
});
|
});
|
||||||
@ -54,7 +62,7 @@ const StillImage: React.FC<IStillImage> = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-testid='still-image-container'
|
data-testid='still-image-container'
|
||||||
className={clsx(className, 'relative isolate overflow-hidden', { 'group': !noGroup })}
|
className={clsx(className, 'relative isolate', { 'group': !noGroup })}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@ -66,6 +74,7 @@ const StillImage: React.FC<IStillImage> = ({
|
|||||||
className={clsx(baseClassName, {
|
className={clsx(baseClassName, {
|
||||||
'invisible group-hover:visible': hoverToPlay,
|
'invisible group-hover:visible': hoverToPlay,
|
||||||
})}
|
})}
|
||||||
|
crossOrigin='anonymous'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{hoverToPlay && (
|
{hoverToPlay && (
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { FastAverageColor } from 'fast-average-color';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
@ -15,22 +16,36 @@ const messages = defineMessages({
|
|||||||
interface IAvatar extends Pick<IStillImage, 'alt' | 'src' | 'onError' | 'className'> {
|
interface IAvatar extends Pick<IStillImage, 'alt' | 'src' | 'onError' | 'className'> {
|
||||||
/** Width and height of the avatar in pixels. */
|
/** Width and height of the avatar in pixels. */
|
||||||
size?: number;
|
size?: number;
|
||||||
|
/** Whether the user is a cat. */
|
||||||
|
isCat?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fac = new FastAverageColor();
|
||||||
|
|
||||||
/** Round profile avatar for accounts. */
|
/** Round profile avatar for accounts. */
|
||||||
const Avatar = (props: IAvatar) => {
|
const Avatar = (props: IAvatar) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { alt, src, size = AVATAR_SIZE, className } = props;
|
const { alt, src, size = AVATAR_SIZE, className, isCat } = props;
|
||||||
|
|
||||||
|
const [color, setColor] = useState<string | undefined>(undefined);
|
||||||
const [isAvatarMissing, setIsAvatarMissing] = useState<boolean>(false);
|
const [isAvatarMissing, setIsAvatarMissing] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleLoadFailure = () => setIsAvatarMissing(true);
|
const handleLoadFailure = () => setIsAvatarMissing(true);
|
||||||
|
|
||||||
|
const handleLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
||||||
|
const color = fac.getColor(e.currentTarget);
|
||||||
|
if (!color.error) {
|
||||||
|
setColor(color.hex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const style: React.CSSProperties = React.useMemo(() => ({
|
const style: React.CSSProperties = React.useMemo(() => ({
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
}), [size]);
|
fontSize: size,
|
||||||
|
color,
|
||||||
|
}), [size, color]);
|
||||||
|
|
||||||
if (isAvatarMissing) {
|
if (isAvatarMissing) {
|
||||||
return (
|
return (
|
||||||
@ -38,8 +53,9 @@ const Avatar = (props: IAvatar) => {
|
|||||||
style={{
|
style={{
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
|
color,
|
||||||
}}
|
}}
|
||||||
className={clsx('flex items-center justify-center rounded-lg bg-gray-200 dark:bg-gray-900', className)}
|
className={clsx('flex items-center justify-center rounded-lg bg-gray-200 leading-[0] dark:bg-gray-900', isCat && 'avatar__cat', className)}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
src={require('@tabler/icons/outline/photo-off.svg')}
|
src={require('@tabler/icons/outline/photo-off.svg')}
|
||||||
@ -51,11 +67,13 @@ const Avatar = (props: IAvatar) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StillImage
|
<StillImage
|
||||||
className={clsx('rounded-lg', className)}
|
className={clsx('rounded-lg leading-[0]', isCat && 'avatar__cat', className)}
|
||||||
|
innerClassName='rounded-lg'
|
||||||
style={style}
|
style={style}
|
||||||
src={src}
|
src={src}
|
||||||
alt={alt || intl.formatMessage(messages.avatar)}
|
alt={alt || intl.formatMessage(messages.avatar)}
|
||||||
onError={handleLoadFailure}
|
onError={handleLoadFailure}
|
||||||
|
onLoad={handleLoad}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -657,6 +657,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||||||
alt={account.avatar_description}
|
alt={account.avatar_description}
|
||||||
size={96}
|
size={96}
|
||||||
className='relative size-24 rounded-lg bg-white ring-4 ring-white black:ring-black dark:bg-primary-900 dark:ring-primary-900'
|
className='relative size-24 rounded-lg bg-white ring-4 ring-white black:ring-black dark:bg-primary-900 dark:ring-primary-900'
|
||||||
|
isCat={account.is_cat}
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
{account.verified && (
|
{account.verified && (
|
||||||
|
|||||||
@ -91,6 +91,7 @@ const Report: React.FC<IReport> = ({ id }) => {
|
|||||||
alt={targetAccount.avatar_description}
|
alt={targetAccount.avatar_description}
|
||||||
size={32}
|
size={32}
|
||||||
className='overflow-hidden'
|
className='overflow-hidden'
|
||||||
|
isCat={targetAccount.is_cat}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</HoverAccountWrapper>
|
</HoverAccountWrapper>
|
||||||
|
|||||||
@ -94,6 +94,7 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
|
|||||||
alt={chat.account.avatar_description}
|
alt={chat.account.avatar_description}
|
||||||
size={40}
|
size={40}
|
||||||
className='flex-none'
|
className='flex-none'
|
||||||
|
isCat={chat.account.is_cat}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Stack alignItems='start' className='overflow-hidden'>
|
<Stack alignItems='start' className='overflow-hidden'>
|
||||||
|
|||||||
@ -186,7 +186,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
|||||||
return (
|
return (
|
||||||
<Stack alignItems='center' justifyContent='center' className='h-full grow'>
|
<Stack alignItems='center' justifyContent='center' className='h-full grow'>
|
||||||
<Stack alignItems='center' space={2}>
|
<Stack alignItems='center' space={2}>
|
||||||
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={75} />
|
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={75} isCat={chat.account.is_cat} />
|
||||||
<Text align='center'>
|
<Text align='center'>
|
||||||
<>
|
<>
|
||||||
<Text tag='span'>{intl.formatMessage(messages.blockedBy)}</Text>
|
<Text tag='span'>{intl.formatMessage(messages.blockedBy)}</Text>
|
||||||
|
|||||||
@ -137,7 +137,7 @@ const ChatPageMain = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Link to={`/@${chat.account.acct}`}>
|
<Link to={`/@${chat.account.acct}`}>
|
||||||
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={40} className='flex-none' />
|
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={40} className='flex-none' isCat={chat.account.is_cat} />
|
||||||
</Link>
|
</Link>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ const ChatPageMain = () => {
|
|||||||
src={require('@tabler/icons/outline/info-circle.svg')}
|
src={require('@tabler/icons/outline/info-circle.svg')}
|
||||||
component={() => (
|
component={() => (
|
||||||
<HStack className='px-4 py-2' alignItems='center' space={3}>
|
<HStack className='px-4 py-2' alignItems='center' space={3}>
|
||||||
<Avatar src={chat.account.avatar_static} alt={chat.account.avatar_description} size={50} />
|
<Avatar src={chat.account.avatar_static} alt={chat.account.avatar_description} size={50} isCat={chat.account.is_cat} />
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text weight='semibold'>{chat.account.display_name}</Text>
|
<Text weight='semibold'>{chat.account.display_name}</Text>
|
||||||
<Text size='sm' theme='primary'>@{chat.account.acct}</Text>
|
<Text size='sm' theme='primary'>@{chat.account.acct}</Text>
|
||||||
|
|||||||
@ -41,7 +41,7 @@ const Results = ({ accountSearchResult, onSelect, parentRef }: IResults) => {
|
|||||||
data-testid='account'
|
data-testid='account'
|
||||||
>
|
>
|
||||||
<HStack alignItems='center' space={2}>
|
<HStack alignItems='center' space={2}>
|
||||||
<Avatar src={account.avatar} alt={account.avatar_description} size={40} />
|
<Avatar src={account.avatar} alt={account.avatar_description} size={40} isCat={account.is_cat} />
|
||||||
|
|
||||||
<Stack alignItems='start'>
|
<Stack alignItems='start'>
|
||||||
<div className='flex grow items-center space-x-1'>
|
<div className='flex grow items-center space-x-1'>
|
||||||
|
|||||||
@ -113,7 +113,7 @@ const ChatSettings = () => {
|
|||||||
|
|
||||||
<Stack space={4} className='mx-auto w-5/6'>
|
<Stack space={4} className='mx-auto w-5/6'>
|
||||||
<HStack alignItems='center' space={3}>
|
<HStack alignItems='center' space={3}>
|
||||||
<Avatar src={chat.account.avatar_static} alt={chat.account.avatar_description} size={50} />
|
<Avatar src={chat.account.avatar_static} alt={chat.account.avatar_description} size={50} isCat={chat.account.is_cat} />
|
||||||
<Stack>
|
<Stack>
|
||||||
<Text weight='semibold'>{chat.account.display_name}</Text>
|
<Text weight='semibold'>{chat.account.display_name}</Text>
|
||||||
<Text size='sm' theme='primary'>@{chat.account.acct}</Text>
|
<Text size='sm' theme='primary'>@{chat.account.acct}</Text>
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const ChatWindow = () => {
|
|||||||
<HStack alignItems='center' space={3}>
|
<HStack alignItems='center' space={3}>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Link to={`/@${chat.account.acct}`}>
|
<Link to={`/@${chat.account.acct}`}>
|
||||||
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={40} />
|
<Avatar src={chat.account.avatar} alt={chat.account.avatar_description} size={40} isCat={chat.account.is_cat} />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -58,6 +58,7 @@ const AccountCard: React.FC<IAccountCard> = ({ id }) => {
|
|||||||
alt={account.avatar_description}
|
alt={account.avatar_description}
|
||||||
className='!absolute bottom-0 left-3 translate-y-1/2 bg-white ring-2 ring-white dark:bg-primary-900 dark:ring-primary-900'
|
className='!absolute bottom-0 left-3 translate-y-1/2 bg-white ring-2 ring-white dark:bg-primary-900 dark:ring-primary-900'
|
||||||
size={64}
|
size={64}
|
||||||
|
isCat={account.is_cat}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</HoverAccountWrapper>
|
</HoverAccountWrapper>
|
||||||
|
|||||||
@ -77,7 +77,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
|||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<Link to={`/@${account.acct}`}>
|
<Link to={`/@${account.acct}`}>
|
||||||
<Avatar src={account.avatar} alt={account.avatar_description} size={42} />
|
<Avatar src={account.avatar} alt={account.avatar_description} size={42} isCat={account.is_cat} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<ComposeForm
|
<ComposeForm
|
||||||
|
|||||||
@ -75,7 +75,7 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
|||||||
<Stack space={10}>
|
<Stack space={10}>
|
||||||
<div className='relative mx-auto rounded-lg bg-gray-200'>
|
<div className='relative mx-auto rounded-lg bg-gray-200'>
|
||||||
{account && (
|
{account && (
|
||||||
<Avatar src={selectedFile || account.avatar} alt={account.avatar_description} size={175} />
|
<Avatar src={selectedFile || account.avatar} alt={account.avatar_description} size={175} isCat={account.is_cat} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isSubmitting && (
|
{isSubmitting && (
|
||||||
|
|||||||
@ -118,6 +118,7 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
src={account.avatar}
|
src={account.avatar}
|
||||||
alt={account.avatar_description}
|
alt={account.avatar_description}
|
||||||
|
isCat={account.is_cat}
|
||||||
size={64}
|
size={64}
|
||||||
className='-mt-8 mb-2 ring-2 ring-white dark:ring-primary-800'
|
className='-mt-8 mb-2 ring-2 ring-white dark:ring-primary-800'
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -53,8 +53,9 @@ const UserPanel: React.FC<IUserPanel> = ({ accountId, action, badges, domain })
|
|||||||
<Avatar
|
<Avatar
|
||||||
src={account.avatar}
|
src={account.avatar}
|
||||||
alt={account.avatar_description}
|
alt={account.avatar_description}
|
||||||
|
isCat={account.is_cat}
|
||||||
size={80}
|
size={80}
|
||||||
className='size-20 overflow-hidden bg-gray-50 ring-2 ring-white'
|
className='size-20 bg-gray-50 ring-2 ring-white'
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ const HomeLayout: React.FC<IHomeLayout> = ({ children }) => {
|
|||||||
<CardBody>
|
<CardBody>
|
||||||
<HStack alignItems='start' space={2}>
|
<HStack alignItems='start' space={2}>
|
||||||
<Link to={`/@${acct}`}>
|
<Link to={`/@${acct}`}>
|
||||||
<Avatar src={avatar} alt={account?.avatar_description} size={42} />
|
<Avatar src={avatar} alt={account?.avatar_description} isCat={account?.is_cat} size={42} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className='w-full translate-y-0.5'>
|
<div className='w-full translate-y-0.5'>
|
||||||
|
|||||||
@ -42,4 +42,61 @@
|
|||||||
|
|
||||||
.underline-line-through {
|
.underline-line-through {
|
||||||
text-decoration: underline line-through;
|
text-decoration: underline line-through;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adapted from Iceshrimp which I guess took it from Misskey
|
||||||
|
// https://iceshrimp.dev/iceshrimp/iceshrimp/src/branch/dev/packages/client/src/components/global/MkAvatar.vue
|
||||||
|
.avatar__cat {
|
||||||
|
img {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
background: #ebbcba;
|
||||||
|
border: solid 0.05em currentcolor;
|
||||||
|
box-sizing: border-box;
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
height: 50%;
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
border-radius: 0 75% 75%;
|
||||||
|
transform: rotate(37.5deg) skew(30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
border-radius: 75% 0 75% 75%;
|
||||||
|
transform: rotate(-37.5deg) skew(-30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::before {
|
||||||
|
animation: earwiggleleft 1s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
animation: earwiggleright 1s infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes earwiggleleft {
|
||||||
|
0% { transform: rotate(37.6deg) skew(30deg); }
|
||||||
|
25% { transform: rotate(10deg) skew(30deg); }
|
||||||
|
50% { transform: rotate(20deg) skew(30deg); }
|
||||||
|
75% { transform: rotate(0deg) skew(30deg); }
|
||||||
|
100% { transform: rotate(37.6deg) skew(30deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes earwiggleright {
|
||||||
|
0% { transform: rotate(-37.6deg) skew(-30deg); }
|
||||||
|
30% { transform: rotate(-10deg) skew(-30deg); }
|
||||||
|
55% { transform: rotate(-20deg) skew(-30deg); }
|
||||||
|
75% { transform: rotate(0deg) skew(-30deg); }
|
||||||
|
100% { transform: rotate(-37.6deg) skew(-30deg); }
|
||||||
|
}
|
||||||
|
|||||||
@ -4787,6 +4787,11 @@ fake-indexeddb@^6.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz#3173d5ad141436dace95f8de6e9ecdc3d9787d5d"
|
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz#3173d5ad141436dace95f8de6e9ecdc3d9787d5d"
|
||||||
integrity sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==
|
integrity sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==
|
||||||
|
|
||||||
|
fast-average-color@^9.5.0:
|
||||||
|
version "9.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-average-color/-/fast-average-color-9.5.0.tgz#deade6bd998c0ea399a519ed1e2cd67ef673f112"
|
||||||
|
integrity sha512-nC6x2YIlJ9xxgkMFMd1BNoM1ctMjNoRKfRliPmiEWW3S6rLTHiQcy9g3pt/xiKv/D0NAAkhb9VyV+WJFvTqMGg==
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
|
||||||
|
|||||||
Reference in New Issue
Block a user