Mp 3250 update health bars (#403)

* [Story] Add HealthBar

* Update tooltip

* Fixes after rebase

* Fixes after rebase

* Fixes after rebase

* Finish health bar + gauge

* Finish health bar + gauge

* Finish health bar + gauge

* feat: added an svg mask to the HealthBar

* fix: added transitions

* tidy: value sanity

* fix: fixed the AccountStats

* tidy: design update

* fix: div adjustments

* update healthguagepercentage function

* make tooltiparrow responsive

---------

Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
Bob van der Helm 2023-08-29 11:17:31 -03:00 committed by GitHub
parent f64d2a78ff
commit e15de26efd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 426 additions and 284 deletions

View File

@ -26,7 +26,11 @@ jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({ actions: []
jest.mock('components/DisplayCurrency')
jest.mock('hooks/useHealthComputer', () => jest.fn(() => ({ computeMaxBorrowAmount: () => {} })))
jest.mock('hooks/useHealthComputer', () =>
jest.fn(() => ({
computeMaxBorrowAmount: () => {},
})),
)
const mockedDisplayCurrency = jest
.mocked(DisplayCurrency)
@ -49,13 +53,6 @@ describe('<VaultBorrowings />', () => {
const defaultProps: VaultBorrowingsProps = {
primaryAsset: ASSETS[0],
secondaryAsset: ASSETS[1],
updatedAccount: {
id: 'test',
deposits: [],
debts: [],
vaults: [],
lends: [],
},
vault: mockedVault,
borrowings: [],
deposits: [],

View File

@ -6,8 +6,8 @@ import AccountComposition from 'components/Account/AccountComposition'
import Card from 'components/Card'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import { Gauge } from 'components/Gauge'
import { ChevronLeft, ChevronRight, Heart } from 'components/Icons'
import { HealthGauge } from 'components/HealthGauge'
import { ChevronLeft, ChevronRight } from 'components/Icons'
import Text from 'components/Text'
import { ORACLE_DENOM } from 'constants/oracle'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
@ -83,7 +83,7 @@ function AccountDetails(props: Props) {
'flex flex-wrap w-16 group/details relative',
'border rounded-base border-white/20',
'bg-white/5 backdrop-blur-sticky transition-colors duration-300',
'hover:bg-white/20 hover:cursor-pointer ',
'hover:bg-white/10 hover:cursor-pointer ',
)}
onClick={() => setIsExpanded(!isExpanded)}
>
@ -97,18 +97,10 @@ function AccountDetails(props: Props) {
{isExpanded ? <ChevronRight className='w-2' /> : <ChevronLeft className='w-2' />}
</div>
<div className='flex flex-wrap justify-center w-full py-4'>
<Gauge tooltip='Health Factor' percentage={health} icon={<Heart />} />
<HealthGauge health={health} />
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
Health
Account Health
</Text>
<div className='flex'>
<FormattedNumber
className={'w-full text-center text-xs'}
amount={health}
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }}
animate
/>
</div>
</div>
<div className='w-full py-4 border-t border-white/20'>
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>

View File

@ -1,62 +0,0 @@
import classNames from 'classnames'
import { Heart } from 'components/Icons'
import Text from 'components/Text'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
health: number
hasLabel?: boolean
classNames?: string
}
export default function AccountHealth(props: Props) {
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
const healthBarWidth = (props.health / 100) * 53
return (
<div className={classNames('flex items-center gap-1.5', props.classNames)}>
<div className='flex flex-auto w-3 h-4'>
<Heart />
</div>
{props.hasLabel && (
<Text size='xs' className='pr-0.5 text-white/70'>
Health
</Text>
)}
<div className='flex flex-shrink'>
<Tooltip content={`Account Health: ${props.health}%`} type='info'>
<svg width='100%' viewBox='0 0 53 4' fill='none' xmlns='http://www.w3.org/2000/svg'>
<rect width='53' height='4' rx='2' fill='white' fillOpacity='0.1' />
<rect
width={healthBarWidth}
height='4'
rx='2'
fill='url(#bar)'
style={{
transition: reduceMotion ? 'none' : 'width 1s ease',
}}
/>
<defs>
<linearGradient
id='bar'
x1='0%'
y1='0%'
x2='100%'
y2='0%'
gradientUnits='userSpaceOnUse'
>
<stop stopColor='#FF645F' />
<stop offset='0.5' stopColor='#FFB1AE' />
<stop offset='1' stopColor='#FFD5D3' />
</linearGradient>
</defs>
</svg>
</Tooltip>
</div>
</div>
)
}

View File

@ -1,9 +1,10 @@
import { useMemo } from 'react'
import AccountHealth from 'components/Account/AccountHealth'
import HealthBar from 'components/Account/HealthBar'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowChartLineUp } from 'components/Icons'
import { ArrowChartLineUp, Heart } from 'components/Icons'
import Text from 'components/Text'
import { ORACLE_DENOM } from 'constants/oracle'
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
import useHealthComputer from 'hooks/useHealthComputer'
@ -40,21 +41,27 @@ export default function AccountStats(props: Props) {
[props.account, borrowAssetsData, lendingAssetsData, prices],
)
return (
<div className='flex-wrap w-full'>
<div className='flex flex-wrap w-full'>
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionBalance)}
className='w-full text-xl'
/>
<div className='flex items-center justify-between w-full mt-1'>
<span className='flex flex-wrap'>
<div className='flex items-center'>
<ArrowChartLineUp className='w-4 mr-1' />
<FormattedNumber
className='text-xs text-white/70'
amount={apr.toNumber()}
options={{ prefix: 'APR: ', suffix: '%', minDecimals: 2, maxDecimals: 2 }}
/>
</span>
<AccountHealth health={health} classNames='w-[140px]' hasLabel />
</div>
<div className='flex items-center gap-1'>
<Heart className='w-3' />
<Text size='xs' className='w-auto mr-1 text-white/70'>
Health
</Text>
<HealthBar health={health} classNames='w-[92px] h-0.5' hasLabel />
</div>
</div>
</div>
)

View File

@ -1,12 +1,12 @@
import { useMemo } from 'react'
import { HTMLAttributes, useMemo } from 'react'
import classNames from 'classnames'
import Accordion from 'components/Accordion'
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
import AccountComposition from 'components/Account/AccountComposition'
import AccountHealth from 'components/Account/AccountHealth'
import HealthBar from 'components/Account/HealthBar'
import Card from 'components/Card'
import DisplayCurrency from 'components/DisplayCurrency'
import { FormattedNumber } from 'components/FormattedNumber'
import Text from 'components/Text'
import { BN_ZERO } from 'constants/math'
import { ORACLE_DENOM } from 'constants/oracle'
@ -17,6 +17,7 @@ import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableDa
import usePrices from 'hooks/usePrices'
import { BNCoin } from 'types/classes/BNCoin'
import { calculateAccountBalanceValue, calculateAccountLeverage } from 'utils/accounts'
import { formatLeverage } from 'utils/formatters'
interface Props {
account: Account
@ -49,24 +50,19 @@ export default function AccountSummary(props: Props) {
if (!props.account) return null
return (
<div className='h-[546px] min-w-[345px] basis-[345px] overflow-y-scroll scrollbar-hide'>
<div className='h-[546px] min-w-[370px] basis-[370px] overflow-y-scroll scrollbar-hide'>
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
<Item title='Networth'>
<Item label='Net worth' classNames='flex-1'>
<DisplayCurrency
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalance)}
className='text-xs'
className='text-sm'
/>
</Item>
<Item title='Leverage'>
<FormattedNumber
className='text-xs'
amount={leverage.toNumber()}
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
animate
/>
<Item label='Leverage' classNames='flex-1'>
<Text size='sm'>{formatLeverage(leverage.toNumber())}</Text>
</Item>
<Item title='Account Health'>
<AccountHealth health={health} />
<Item label='Account health'>
<HealthBar health={health} classNames='w-[184px] h-1' />
</Item>
</Card>
<Accordion
@ -100,16 +96,24 @@ export default function AccountSummary(props: Props) {
)
}
function Item(props: React.HTMLAttributes<HTMLDivElement>) {
interface ItemProps extends HTMLAttributes<HTMLDivElement> {
label: string
classNames?: string
}
function Item(props: ItemProps) {
return (
<div
className='flex flex-wrap items-center gap-1 px-4 py-2 border-r border-r-white/10'
className={classNames(
'flex flex-col justify-around px-3 py-1 border-r border-r-white/10',
props.classNames,
)}
{...props}
>
<Text size='xs' className='w-full text-white/50'>
{props.title}
<Text size='2xs' className='text-white/50 whitespace-nowrap'>
{props.label}
</Text>
{props.children}
<div className='flex h-4.5 w-full'>{props.children}</div>
</div>
)
}

View File

@ -0,0 +1,53 @@
import classNames from 'classnames'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage'
interface Props {
health: number
hasLabel?: boolean
classNames?: string
}
function calculateHealth(health: number) {
const firstBarEnd = 43
const secondBarStart = 46
const secondBarEnd = 93
const thirdBarStart = 96
const thirdBarEnd = 184
if (health <= 10) return (firstBarEnd / 10) * health
if (health <= 30) return ((secondBarEnd - secondBarStart) / 20) * (health - 10) + secondBarStart
if (health <= 100) return ((thirdBarEnd - thirdBarStart) / 70) * (health - 30) + thirdBarStart
}
export default function HealthBar(props: Props) {
const { health } = props
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
const [color, label] = useHealthColorAndLabel(health, 'fill-')
const width = calculateHealth(health)
return (
<Tooltip content={label} type='info' className='flex items-center w-full'>
<div className={classNames('flex max-w-[184px] max-h-1', props.classNames)}>
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 184 4'>
<mask id='healthBarMask'>
<path fill='#FFFFFF' d='M0,2c0-1.1,0.9-2,2-2h41.6v4H2C0.9,4,0,3.1,0,2z' />
<rect x='46' fill='#FFFFFF' width='47' height='4' />
<path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' />
</mask>
<rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' />
<rect
className={classNames(color, !reduceMotion && 'transition-all duration-500')}
width={width}
height='4'
mask='url(#healthBarMask)'
/>
</svg>
</div>
</Tooltip>
)
}

View File

@ -0,0 +1,96 @@
import classNames from 'classnames'
import { useMemo } from 'react'
import { Heart } from 'components/Icons'
import { Tooltip } from 'components/Tooltip'
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
import { REDUCE_MOTION_KEY } from 'constants/localStore'
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
import useLocalStorage from 'hooks/useLocalStorage'
import { computeHealthGaugePercentage } from 'utils/accounts'
interface Props {
diameter?: number
health: number
}
const RADIUS = 350
const ROTATION = {
rotate: '-126deg',
transformOrigin: 'center',
}
export const HealthGauge = ({ diameter = 40, health = 0 }: Props) => {
const [color, label] = useHealthColorAndLabel(health, 'text-')
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
const percentage = useMemo(() => computeHealthGaugePercentage(health), [health])
return (
<Tooltip type='info' content={label}>
<div
className={classNames(
'relative grid place-items-center',
`w-${diameter / 4} h-${diameter / 4}`,
)}
>
<Heart className='text-white/50' width={20} />
<svg
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
viewBox='0 0 700 700'
className='absolute'
>
<mask id='mask'>
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
<path
fill='white'
d='M585.2,594.5C577.4,594.5 569.5,591.7 563.3,586.1C549.9,574 548.8,553.3 560.9,539.8C601.3,494.9 626,439.5 632.3,379.4C638.6,319.3 626,259.9 595.8,207.7C586.8,192 592.1,172 607.8,162.9C623.5,153.8 643.5,159.2 652.6,174.9C689.8,239.2 705.3,312.3 697.5,386.3C689.7,460.2 659.3,528.5 609.6,583.7C603,590.9 594.1,594.5 585.2,594.5Z'
/>
</g>
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
<path
fill='white'
d='M561.7,147C553.9,147 546,144.2 539.8,138.6C494.9,98.2 439.5,73.5 379.4,67.2C319.4,60.9 259.9,73.5 207.6,103.7C191.9,112.8 171.9,107.4 162.8,91.7C153.7,76 159.1,56 174.8,46.9C239.2,9.7 312.3,-5.8 386.2,2C460.1,9.8 528.4,40.2 583.6,89.9C597,102 598.1,122.7 586,136.2C579.6,143.3 570.7,147 561.7,147Z'
/>
</g>
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
<path
fill='white'
d='M349.6,699.3C328.4,699.3 307.1,697.4 285.9,693.5C204.1,678.3 130.2,634.4 77.9,569.8C25.6,505.2 -2.1,423.8 0.1,340.6C2.3,257.4 34.1,177.6 89.8,115.8C101.9,102.4 122.7,101.3 136.1,113.4C149.5,125.5 150.6,146.2 138.5,159.7C93.3,209.9 67.4,274.8 65.6,342.4C63.8,410 86.3,476.1 128.8,528.6C171.3,581.1 231.4,616.8 297.8,629.2C364.2,641.5 433.1,629.7 491.6,595.9C507.3,586.8 527.3,592.2 536.4,607.9C545.4,623.6 540.1,643.6 524.4,652.7C471,683.4 410.6,699.3 349.6,699.3Z'
/>
</g>
</mask>
<circle
r={RADIUS}
cx={RADIUS}
cy={RADIUS}
fill='white'
fillOpacity={0.2}
mask='url(#mask)'
style={ROTATION}
/>
<circle
r={316}
cx={RADIUS}
cy={RADIUS}
fill='transparent'
strokeWidth={64}
pathLength={100}
strokeDasharray={100}
strokeDashoffset={percentage}
strokeLinecap='round'
style={ROTATION}
mask='url(#mask)'
className={classNames(
color,
'stroke-current',
!reduceMotion && 'transition-color transition-[stroke-dashoffset] duration-500',
)}
/>
</svg>
</div>
</Tooltip>
)
}

View File

@ -0,0 +1,5 @@
<svg viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.86824 5.48057C4.48435 6.15239 3.51565 6.15239 3.13176 5.48057L0 0L8 0L4.86824 5.48057Z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 219 B

View File

@ -46,6 +46,7 @@ export { default as StarFilled } from 'components/Icons/StarFilled.svg'
export { default as StarOutlined } from 'components/Icons/StarOutlined.svg'
export { default as Subtract } from 'components/Icons/Subtract.svg'
export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
export { default as TooltipArrow } from 'components/Icons/TooltipArrow.svg'
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
export { default as VerticalThreeLine } from 'components/Icons/VerticalThreeLine.svg'
export { default as Wallet } from 'components/Icons/Wallet.svg'

View File

@ -14,7 +14,7 @@ import { BN_ZERO } from 'constants/math'
import usePrice from 'hooks/usePrice'
import useStore from 'store'
import { BNCoin } from 'types/classes/BNCoin'
import { accumulateAmounts, getAmount } from 'utils/accounts'
import { accumulateAmounts } from 'utils/accounts'
import { findCoinByDenom } from 'utils/assets'
import { BN } from 'utils/helpers'

View File

@ -1,7 +1,7 @@
import classNames from 'classnames'
import { ReactNode } from 'react'
import { ExclamationMarkCircled } from 'components/Icons'
import { TooltipArrow } from 'components/Icons'
import Text from 'components/Text'
import { TooltipType } from '.'
@ -13,19 +13,35 @@ interface Props {
export default function TooltipContent(props: Props) {
return (
<div
className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-xs shadow-tooltip backdrop-blur-lg',
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
props.type === 'info' && 'bg-white/20',
props.type === 'warning' && 'bg-warning',
props.type === 'error' && 'bg-error',
)}
>
<div>
<ExclamationMarkCircled className='w-5 gap-3 text-white' />
<div>
<div
className={classNames(
'flex max-w-[320px] flex-1 gap-2 rounded-sm py-0.5 px-2 text-sm shadow-tooltip backdrop-blur-lg',
props.type === 'info' && 'bg-white/20',
props.type === 'warning' && 'bg-warning',
props.type === 'error' && 'bg-error',
)}
>
{typeof props.content === 'string' ? (
<Text size='sm' className='font-semibold'>
{props.content}
</Text>
) : (
props.content
)}
</div>
{typeof props.content === 'string' ? <Text size='sm'>{props.content}</Text> : props.content}
{
<div data-popper-arrow=''>
<TooltipArrow
width={8}
className={classNames(
props.type === 'info' && 'text-white/20',
props.type === 'warning' && 'text-warning',
props.type === 'error' && 'text-error',
)}
/>
</div>
}
</div>
)
}

View File

@ -0,0 +1,5 @@
export default function useHealthColorAndLabel(health: number, colorPrefix: string) {
if (health > 30) return [`${colorPrefix}violet-500`, 'Healthy']
if (health > 10) return [`${colorPrefix}yellow-300`, 'Attention']
return [`${colorPrefix}martian-red`, 'Liquidation risk']
}

View File

@ -70,7 +70,7 @@ export const calculateAccountApr = (
let totalLendsInterestValue = BN_ZERO
let totalVaultsInterestValue = BN_ZERO
let totalDeptsInterestValue = BN_ZERO
let totalDebtInterestValue = BN_ZERO
lends?.forEach((lend) => {
const asset = getAssetByDenom(lend.denom)
@ -104,15 +104,13 @@ export const calculateAccountApr = (
borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)?.borrowRate ??
0
const positionInterest = amount.multipliedBy(price).multipliedBy(apr)
totalDeptsInterestValue = totalDeptsInterestValue.plus(positionInterest)
totalDebtInterestValue = totalDebtInterestValue.plus(positionInterest)
})
const totalInterstValue = totalLendsInterestValue
.plus(totalVaultsInterestValue)
.minus(totalDeptsInterestValue)
const totalApr = totalInterstValue.dividedBy(totalValue).times(100)
return totalApr
.minus(totalDebtInterestValue)
return totalInterstValue.dividedBy(totalValue).times(100)
}
export const calculateAccountBorrowRate = (
@ -206,3 +204,27 @@ export function cloneAccount(account: Account): Account {
})),
}
}
export function computeHealthGaugePercentage(health: number) {
const ATTENTION_CUTOFF = 10
const HEALTHY_CUTOFF = 30
const HEALTHY_BAR_SIZE = 55
const UNHEALTHY_BAR_SIZE = 21
const GAP_SIZE = 3
if (health > HEALTHY_CUTOFF) {
const basePercentage = 100 - HEALTHY_BAR_SIZE
const additionalPercentage =
((health - HEALTHY_CUTOFF) / (100 - HEALTHY_CUTOFF)) * HEALTHY_BAR_SIZE
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
}
if (health > ATTENTION_CUTOFF) {
const basePercentage = UNHEALTHY_BAR_SIZE
const additionalPercentage =
((health - ATTENTION_CUTOFF) / (HEALTHY_CUTOFF - ATTENTION_CUTOFF)) * UNHEALTHY_BAR_SIZE
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
}
return 100 - (health / ATTENTION_CUTOFF) * UNHEALTHY_BAR_SIZE
}

View File

@ -27,6 +27,11 @@ module.exports = {
'text-4xl',
'text-5xl-caps',
'text-5xl',
'text-yellow-300',
'text-violet-500',
'fill-yellow-300',
'fill-violet-500',
'fill-martian-red',
'w-2',
],
theme: {
@ -60,7 +65,7 @@ module.exports = {
overlay: '0 2px 2px rgba(0, 0, 0, 0.14), 0 1px 5px rgba(0, 0, 0, 0.2)',
button: '0px 1px 1px rgba(0, 0, 0, 0.14), 0px 1px 3px rgba(0, 0, 0, 0.2)',
tooltip:
'0 3px 4px rgba(0, 0, 0, 0.14), 0 3px 3px rgba(0, 0, 0, 0.12), 0 1px 8px rgba(0, 0, 0, 0.2)',
'0 3px 4px rgba(0, 0, 0, 0.04), 0 3px 3px rgba(0, 0, 0, 0.04), 0 1px 8px rgba(0, 0, 0, 0.04)',
},
brightness: {
30: '.3',
@ -222,160 +227,161 @@ module.exports = {
h2: { fontSize: '9px', lineHeight: '56px' },
h3: { fontSize: '30px', lineHeight: '40px' },
h4: { fontSize: '24px', lineHeight: '36px', fontWeight: theme('fontWeight.normal') },
}),
addUtilities({
'.blur-orb-primary': {
filter: 'blur(clamp(50px, 8vw, 100px))',
},
'.blur-orb-secondary': {
filter: 'blur(clamp(60px, 20vw, 140px))',
},
'.blur-orb-tertiary': {
filter: 'blur(clamp(60px, 10vw, 110px))',
},
'.border-glas': {
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0))',
mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
'-webkit-mask-composite': 'xor',
maskComposite: 'exclude',
},
'.glow-line': {
x: 0,
y: 0,
fill: 'transparent',
stroke: '#FFF',
strokeWidth: '0.5',
strokeDasharray: '20px 30px',
},
'.glow-hover': {
strokeDashoffset: '-80px',
transition: 'stroke-dashoffset 1000ms ease-in',
},
'.gradient-atom': {
background: 'linear-gradient(to bottom, #2e3148, #6f7390)',
},
'.gradient-axlusdc': {
background: 'linear-gradient(to bottom, #1f5c9e, #478edc)',
},
'.gradient-card': {
background:
'linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))',
},
'.gradient-card-content': {
backgroundImage: 'linear-gradient(to right, transparent, rgba(255, 255, 255, 0.05))',
},
'.gradient-hatched': {
backgroundImage:
'linear-gradient(135deg,transparent 33.33%,#826d6b 33.33%,#826d6b 50%,transparent 50%,transparent 83.33%,#826d6b 83.33%,#826d6b 100%)',
backgroundSize: '5px 5px',
},
'.gradient-header': {
background:
'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%)',
},
'.gradient-limit': {
background:
'linear-gradient(to right,#15bfa9 20.9%,#5e4bb1 49.68%,#382685 82.55%,#c83333 100%)',
},
'.gradient-mars': {
background: 'linear-gradient(to bottom, #a03b45, #c83333)',
},
'.gradient-osmo': {
background: 'linear-gradient(to bottom, #3a02e2, #e700ca)',
},
'.gradient-popover': {
background:
'linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05))',
},
'.gradient-primary-to-secondary': {
background: 'linear-gradient(180deg, #7F78E8 0%, #926AC8 100%)',
},
'.gradient-secondary-to-primary': {
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
},
'.gradient-tooltip': {
background:
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
},
'.gradient-active-tab': {
background:
'linear-gradient(270deg, rgba(186, 8, 189, 0.764896) 0%, rgba(255, 160, 187, 0.88641) 23.77%, rgba(48, 29, 24, 0.26) 99.2%)',
},
'.number': {
whiteSpace: 'nowrap',
fontFeatureSettings: '"tnum" on',
},
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
'.text-3xs-caps': {
fontSize: '9px',
lineHeight: '12px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wide'),
},
'.text-2xs': { fontSize: '10px', lineHeight: '16px' },
'.text-2xs-caps': {
fontSize: '10px',
lineHeight: '16px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wide'),
},
'.text-xs-caps': {
fontSize: '12px',
lineHeight: '16px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-sm-caps': {
fontSize: '14px',
lineHeight: '20px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-base-caps': {
fontSize: '16px',
lineHeight: '20px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-lg-caps': {
fontSize: '17px',
lineHeight: '24px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-xl-caps': {
fontSize: '19px',
lineHeight: '28px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.light'),
letterSpacing: theme('letterSpacing.widest'),
},
'.text-2xl-caps': {
fontSize: '21px',
lineHeight: '32px',
textTransform: 'uppercase',
letterSpacing: theme('letterSpacing.wider'),
},
'.text-3xl-caps': {
fontSize: '24px',
lineHeight: '36px',
textTransform: 'uppercase',
letterSpacing: theme('letterSpacing.wider'),
},
'.text-4xl-caps': { fontSize: '30px', lineHeight: '40px', textTransform: 'uppercase' },
'.text-5xl-caps': {
fontSize: '39px',
lineHeight: '56px',
textTransform: 'uppercase',
letterSpacing: '9px',
},
})
})
addUtilities({
'.blur-orb-primary': {
filter: 'blur(clamp(50px, 8vw, 100px))',
},
'.blur-orb-secondary': {
filter: 'blur(clamp(60px, 20vw, 140px))',
},
'.blur-orb-tertiary': {
filter: 'blur(clamp(60px, 10vw, 110px))',
},
'.border-glas': {
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0))',
mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
'-webkit-mask-composite': 'xor',
maskComposite: 'exclude',
},
'.glow-line': {
x: 0,
y: 0,
fill: 'transparent',
stroke: '#FFF',
strokeWidth: '0.5',
strokeDasharray: '20px 30px',
},
'.glow-hover': {
strokeDashoffset: '-80px',
transition: 'stroke-dashoffset 1000ms ease-in',
},
'.gradient-atom': {
background: 'linear-gradient(to bottom, #2e3148, #6f7390)',
},
'.gradient-axlusdc': {
background: 'linear-gradient(to bottom, #1f5c9e, #478edc)',
},
'.gradient-card': {
background:
'linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))',
},
'.gradient-card-content': {
backgroundImage: 'linear-gradient(to right, transparent, rgba(255, 255, 255, 0.05))',
},
'.gradient-hatched': {
backgroundImage:
'linear-gradient(135deg,transparent 33.33%,#826d6b 33.33%,#826d6b 50%,transparent 50%,transparent 83.33%,#826d6b 83.33%,#826d6b 100%)',
backgroundSize: '5px 5px',
},
'.gradient-header': {
background:
'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%)',
},
'.gradient-limit': {
background:
'linear-gradient(to right,#15bfa9 20.9%,#5e4bb1 49.68%,#382685 82.55%,#c83333 100%)',
},
'.gradient-mars': {
background: 'linear-gradient(to bottom, #a03b45, #c83333)',
},
'.gradient-osmo': {
background: 'linear-gradient(to bottom, #3a02e2, #e700ca)',
},
'.gradient-popover': {
background:
'linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05))',
},
'.gradient-primary-to-secondary': {
background: 'linear-gradient(180deg, #7F78E8 0%, #926AC8 100%)',
},
'.gradient-secondary-to-primary': {
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
},
'.gradient-tooltip': {
background:
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
},
'.gradient-active-tab': {
background:
'linear-gradient(270deg, rgba(186, 8, 189, 0.764896) 0%, rgba(255, 160, 187, 0.88641) 23.77%, rgba(48, 29, 24, 0.26) 99.2%)',
},
'.number': {
whiteSpace: 'nowrap',
fontFeatureSettings: '"tnum" on',
},
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
'.text-3xs-caps': {
fontSize: '9px',
lineHeight: '12px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wide'),
},
'.text-2xs': { fontSize: '10px', lineHeight: '16px' },
'.text-2xs-caps': {
fontSize: '10px',
lineHeight: '16px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wide'),
},
'.text-xs-caps': {
fontSize: '12px',
lineHeight: '16px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-sm-caps': {
fontSize: '14px',
lineHeight: '20px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-base-caps': {
fontSize: '16px',
lineHeight: '20px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-lg-caps': {
fontSize: '17px',
lineHeight: '24px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.semibold'),
letterSpacing: theme('letterSpacing.wider'),
},
'.text-xl-caps': {
fontSize: '19px',
lineHeight: '28px',
textTransform: 'uppercase',
fontWeight: theme('fontWeight.light'),
letterSpacing: theme('letterSpacing.widest'),
},
'.text-2xl-caps': {
fontSize: '21px',
lineHeight: '32px',
textTransform: 'uppercase',
letterSpacing: theme('letterSpacing.wider'),
},
'.text-3xl-caps': {
fontSize: '24px',
lineHeight: '36px',
textTransform: 'uppercase',
letterSpacing: theme('letterSpacing.wider'),
},
'.text-4xl-caps': { fontSize: '30px', lineHeight: '40px', textTransform: 'uppercase' },
'.text-5xl-caps': {
fontSize: '39px',
lineHeight: '56px',
textTransform: 'uppercase',
letterSpacing: '9px',
},
})
}),
],
}