diff --git a/public/configs/v1/env.json b/public/configs/v1/env.json index 27e491af9..2f968df21 100644 --- a/public/configs/v1/env.json +++ b/public/configs/v1/env.json @@ -462,7 +462,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4dev.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -504,7 +505,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -549,7 +551,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "http://dev3-faucet-lb-public-1644791410.us-east-2.elb.amazonaws.com", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -594,7 +597,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4dev4.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -636,7 +640,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -680,7 +685,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geo": "https://api.dydx.exchange/v4/geo", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -724,7 +730,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "apps": { "ios": { @@ -780,7 +787,8 @@ "neutronValidator": "https://neutron-testnet-rpc.polkachu.com/", "geoV2": "https://geo-whitelist-web-mainnet-preview.infrastructure-34d.workers.dev/", "geo": "https://api.dydx.exchange/v4/geo", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -826,7 +834,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-staging-e2fb353831a4.herokuapp.com" + "spotApi": "https://dydx-solana-api-staging-e2fb353831a4.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [ "dydxvaloper1vvc9vl6z9pu0vt2y79d0ln8zp6qmpmrhxx99h4", @@ -874,7 +883,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -919,7 +929,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.dydx.trade/" }, "stakingValidators": [], "featureFlags": { @@ -964,7 +975,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1009,7 +1021,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1054,7 +1067,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1099,7 +1113,8 @@ "stakingAPR": "https://apybara-proxy-web-testnet.infrastructure-34d.workers.dev/v0/protocols/dydx", "faucet": "https://faucet.v4testnet.dydx.exchange", "affiliates": "https://dydx.stg.fuul.xyz", - "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com" + "spotApi": "https://dydx-solana-api-prod-89bf4c933ba0.herokuapp.com", + "pnlImageApi": "https://image-generator.bonk.trade/generate-trade-card-web" }, "stakingValidators": [], "featureFlags": { @@ -1144,7 +1159,8 @@ "geoV2": "[geo v2 endpoint for mainnet]", "stakingAPR": "[staking APR endpoint for mainnet]", "affiliates": "[affiliates endpoint for mainnet]", - "spotApi": "[spot api endpoint for mainnet]" + "spotApi": "[spot api endpoint for mainnet]", + "pnlImageApi": "[pnl image api endpoint for mainnet]" }, "stakingValidators": [], "featureFlags": { diff --git a/src/hooks/useEndpointsConfig.ts b/src/hooks/useEndpointsConfig.ts index 2f724102f..49f71b356 100644 --- a/src/hooks/useEndpointsConfig.ts +++ b/src/hooks/useEndpointsConfig.ts @@ -19,6 +19,7 @@ export interface EndpointsConfig { affiliates?: string; spotApi: string; geoV2: string; + pnlImageApi: string; } export const useEndpointsConfig = () => { @@ -38,5 +39,6 @@ export const useEndpointsConfig = () => { affiliatesBaseUrl: endpointsConfig.affiliates, spotApi: endpointsConfig.spotApi, geoV2: endpointsConfig.geoV2, + pnlImageApi: endpointsConfig.pnlImageApi, }; }; diff --git a/src/hooks/useSharePnlImage.ts b/src/hooks/useSharePnlImage.ts new file mode 100644 index 000000000..117eda081 --- /dev/null +++ b/src/hooks/useSharePnlImage.ts @@ -0,0 +1,110 @@ +import { logBonsaiError } from '@/bonsai/logs'; +import { useQuery } from '@tanstack/react-query'; + +import { timeUnits } from '@/constants/time'; +import { IndexerPositionSide } from '@/types/indexer/indexerApiGen'; + +import { useAccounts } from '@/hooks/useAccounts'; + +import { getOpenPositions } from '@/state/accountSelectors'; +import { useAppSelector } from '@/state/appTypes'; + +import { Nullable } from '@/lib/typeUtils'; +import { truncateAddress } from '@/lib/wallet'; + +import { useEndpointsConfig } from './useEndpointsConfig'; + +export type SharePnlImageParams = { + marketId: string; + side: Nullable; + leverage: Nullable; + oraclePrice: Nullable; + entryPrice: Nullable; + unrealizedPnl: Nullable; + type?: 'open' | 'closed' | 'liquidated' | undefined; +}; + +export const useSharePnlImage = ({ + marketId, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type = 'open', +}: SharePnlImageParams) => { + const { pnlImageApi } = useEndpointsConfig(); + + // Get user wallet address for username + const { dydxAddress } = useAccounts(); + + // Get full position data from state + const openPositions = useAppSelector(getOpenPositions); + const position = openPositions?.find((p) => p.market === marketId); + + const queryFn = async (): Promise => { + if (!dydxAddress) { + return undefined; + } + + // Build the request body matching the API's zod schema + const requestBody = { + brand: 'bonk', + ticker: marketId, + type, + leverage: leverage ?? 0, + username: truncateAddress(dydxAddress), + isLong: side === IndexerPositionSide.LONG, + isCross: position?.marginMode === 'CROSS', + // Optional fields - include if available + size: position?.unsignedSize.toNumber(), + userImage: 'https://dydx.trade/hedgie-profile.png', + pnl: position?.realizedPnl.toNumber(), + uPnl: unrealizedPnl ?? undefined, + pnlPercentage: position?.updatedUnrealizedPnlPercent?.toNumber(), + entryPx: entryPrice ?? undefined, + liquidationPx: position?.liquidationPrice?.toNumber(), + markPx: oraclePrice ?? undefined, + }; + + const response = await fetch(pnlImageApi, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + logBonsaiError('useSharePnlImage', 'Failed to fetch share image', { response }); + throw new Error(`Failed to fetch share image: ${response.status}`); + } + + return response.blob(); + }; + + return useQuery({ + queryKey: [ + 'sharePnlImage', + marketId, + dydxAddress, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type, + position?.marginMode, + position?.unsignedSize.toString(), + position?.liquidationPrice?.toString(), + ], + queryFn, + enabled: Boolean(dydxAddress), + refetchOnWindowFocus: false, + refetchOnReconnect: false, + staleTime: 2 * timeUnits.minute, // 2 minutes + retry: 2, + retryDelay: 1 * timeUnits.second, + retryOnMount: true, + }); +}; diff --git a/src/views/dialogs/SharePNLAnalyticsDialog.tsx b/src/views/dialogs/SharePNLAnalyticsDialog.tsx index 94ae1f72e..e6e0ac01a 100644 --- a/src/views/dialogs/SharePNLAnalyticsDialog.tsx +++ b/src/views/dialogs/SharePNLAnalyticsDialog.tsx @@ -1,38 +1,28 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useMemo, useRef, useState } from 'react'; -import { BonsaiHelpers } from '@/bonsai/ontology'; -import { useToBlob } from '@hugocxl/react-to-image'; import styled from 'styled-components'; import tw from 'twin.macro'; import { AnalyticsEvents } from '@/constants/analytics'; -import { ASSET_ICON_MAP } from '@/constants/assets'; import { ButtonAction } from '@/constants/buttons'; import { DialogProps, SharePNLAnalyticsDialogProps } from '@/constants/dialogs'; import { STRING_KEYS } from '@/constants/localization'; -import { IndexerPositionSide } from '@/types/indexer/indexerApiGen'; -import { useCustomNotification } from '@/hooks/useCustomNotification'; -import { useAppSelectorWithArgs } from '@/hooks/useParameterizedSelector'; +import { useSharePnlImage } from '@/hooks/useSharePnlImage'; import { useStringGetter } from '@/hooks/useStringGetter'; -import { LogoShortIcon } from '@/icons/logo-short'; import { layoutMixins } from '@/styles/layoutMixins'; -import { AssetIcon } from '@/components/AssetIcon'; import { Button } from '@/components/Button'; import { Dialog } from '@/components/Dialog'; import { Icon, IconName } from '@/components/Icon'; -import { Output, OutputType, ShowSign } from '@/components/Output'; -import { QrCode } from '@/components/QrCode'; -import { Tag, TagSign } from '@/components/Tag'; +import { LoadingSpinner } from '@/components/Loading/LoadingSpinner'; import { useAppDispatch } from '@/state/appTypes'; import { closeDialog } from '@/state/dialogs'; import { track } from '@/lib/analytics/analytics'; import { getDisplayableAssetFromBaseAsset } from '@/lib/assetUtils'; -import { MustBigNumber } from '@/lib/numbers'; import { triggerTwitterIntent } from '@/lib/twitter'; const copyBlobToClipboard = async (blob: Blob | null) => { @@ -54,7 +44,6 @@ export const SharePNLAnalyticsDialog = ({ marketId, assetId, side, - sideLabel, leverage, oraclePrice, entryPrice, @@ -63,242 +52,107 @@ export const SharePNLAnalyticsDialog = ({ }: DialogProps) => { const stringGetter = useStringGetter(); const dispatch = useAppDispatch(); - const logoUrl = useAppSelectorWithArgs(BonsaiHelpers.assets.selectAssetLogo, assetId); const symbol = getDisplayableAssetFromBaseAsset(assetId); - const notify = useCustomNotification(); + const isCopying = useRef(false); + const isSharing = useRef(false); const [isCopied, setIsCopied] = useState(false); - const [{ isLoading: isCopying }, convert, ref] = useToBlob({ - quality: 1.0, - onSuccess: async (blob) => { - await copyBlobToClipboard(blob); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - }, - onError: (error) => { - // eslint-disable-next-line no-console - console.error('Failed to copy blob. ', error); - notify({ - title: stringGetter({ key: STRING_KEYS.ERROR }), - body: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG }), - slotTitleLeft: , - toastDuration: 5000, - }); - }, + const getPnlImage = useSharePnlImage({ + marketId, + side, + leverage, + oraclePrice, + entryPrice, + unrealizedPnl, + type: 'open', }); - const [{ isLoading: isSharing }, convertShare, refShare] = useToBlob({ - quality: 1.0, - onSuccess: async (blob) => { - await copyBlobToClipboard(blob); - - triggerTwitterIntent({ - text: `${stringGetter({ - key: STRING_KEYS.TWEET_MARKET_POSITION, - params: { - MARKET: symbol, - }, - })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, - related: 'bonk_inu', - }); - - dispatch(closeDialog()); - }, - }); - - const sideSign = useMemo(() => { - switch (side) { - case IndexerPositionSide.LONG: - return TagSign.Positive; - case IndexerPositionSide.SHORT: - return TagSign.Negative; - default: - return TagSign.Neutral; - } - }, [side]); - - const unrealizedPnlIsNegative = MustBigNumber(unrealizedPnl).isNegative(); - - const [assetLeft, assetRight] = marketId.split('-'); - - const [logoBase64, setLogoBase64] = useState(null); - - const localLogoUrl = useMemo(() => { - if (assetId && Object.prototype.hasOwnProperty.call(ASSET_ICON_MAP, assetId)) { - return ASSET_ICON_MAP[assetId as keyof typeof ASSET_ICON_MAP]; - } - return logoUrl; - }, [logoUrl, assetId]); - - useEffect(() => { - if (!logoUrl) return; - - const img = new Image(); - img.crossOrigin = 'anonymous'; - img.src = logoUrl; - img.onload = () => { - const canvas = document.createElement('canvas'); - canvas.width = img.width || 26; - canvas.height = img.height || 26; - const ctx = canvas.getContext('2d'); - ctx?.drawImage(img, 0, 0, canvas.width, canvas.height); - setLogoBase64(canvas.toDataURL('image/png')); - }; - img.onerror = () => { - // eslint-disable-next-line no-console - console.error('Failed to load asset image. ', logoUrl); - setLogoBase64(null); - }; - }, [logoUrl]); + const pnlImage = useMemo(() => getPnlImage.data ?? undefined, [getPnlImage.data]); + + const copyPnlImage = async () => { + if (isCopying.current || !pnlImage) return; + isCopying.current = true; + await copyBlobToClipboard(pnlImage); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + isCopying.current = false; + }; + + const sharePnlImage = async () => { + if (isSharing.current || !pnlImage) return; + isSharing.current = true; + await copyBlobToClipboard(pnlImage); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + + triggerTwitterIntent({ + text: `${stringGetter({ + key: STRING_KEYS.TWEET_MARKET_POSITION, + params: { + MARKET: symbol, + }, + })}\n\n#bonk_trade #${symbol}\n[${stringGetter({ key: STRING_KEYS.TWEET_PASTE_IMAGE_AND_DELETE_THIS })}]`, + related: 'bonk_inu', + }); + isSharing.current = false; + + dispatch(closeDialog()); + }; return ( - <$ShareableCard - ref={(domNode) => { - if (domNode) { - ref(domNode); - refShare(domNode); - } - }} - > -
-
- - - - {assetLeft}/{assetRight} - - - {sideLabel} + <$ShareableCard> + {!pnlImage ? ( +
+
- - <$HighlightOutput - isNegative={unrealizedPnlIsNegative} - type={OutputType.CompactFiat} - value={unrealizedPnl} - showSign={ShowSign.Both} + ) : ( + Shareable PNL Card -
- -
-
- -
-
- <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.ENTRY })} - - <$ShareableCardStatOutput type={OutputType.Fiat} value={entryPrice} withSubscript /> - - <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.INDEX })} - - <$ShareableCardStatOutput type={OutputType.Fiat} value={oraclePrice} withSubscript /> - - <$ShareableCardStatLabel> - {stringGetter({ key: STRING_KEYS.LEVERAGE })} - - - <$ShareableCardStatOutput - type={OutputType.Multiple} - value={leverage} - showSign={ShowSign.None} - /> -
- -
- {import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL ? ( - <$QrCode - tw="rounded-0.25 bg-color-layer-3" - size={68} - value={import.meta.env.VITE_SHARE_PNL_ANALYTICS_URL} - options={{ - cells: { - fill: 'var(--color-text-2)', - }, - finder: { - fill: 'var(--color-text-2)', - }, - }} - /> - ) : ( -
- )} -
+ )} + +
+ <$Action + action={ButtonAction.Secondary} + slotLeft={} + onClick={() => { + track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); + copyPnlImage(); + }} + state={{ + isLoading: !!isCopying.current, + }} + > + {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} + + <$Action + action={ButtonAction.Primary} + slotLeft={} + onClick={() => { + track(AnalyticsEvents.SharePnlShared({ asset: assetId })); + sharePnlImage(); + }} + state={{ + isLoading: !!isSharing.current, + }} + > + {stringGetter({ key: STRING_KEYS.SHARE })} +
- -
- <$Action - action={ButtonAction.Secondary} - slotLeft={} - onClick={() => { - track(AnalyticsEvents.SharePnlCopied({ asset: assetId })); - convert(); - }} - state={{ - isLoading: isCopying, - }} - > - {stringGetter({ key: isCopied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })} - - <$Action - action={ButtonAction.Primary} - slotLeft={} - onClick={() => { - track(AnalyticsEvents.SharePnlShared({ asset: assetId })); - convertShare(); - }} - state={{ - isLoading: isSharing, - }} - > - {stringGetter({ key: STRING_KEYS.SHARE })} - -
); }; + const $Action = tw(Button)`flex-1`; const $ShareableCard = styled.div` - ${layoutMixins.row} + ${layoutMixins.column} gap: 0.5rem; justify-content: space-between; align-items: flex-start; - margin-bottom: 1.25rem; - background-color: var(--color-layer-4); - padding: 1.75rem 1.25rem 1.25rem 1.25rem; border-radius: 0.5rem; `; -const $ShareableCardStatLabel = tw.div`text-right text-color-text-0 font-base-bold`; - -const $ShareableCardStatOutput = tw(Output)`font-base-bold text-color-text-2`; -const $QrCode = styled(QrCode)` - width: 5.25rem; - height: 5.25rem; - margin-top: 1rem; - margin-left: auto; - - svg { - border: none; - } -`; - -const $HighlightOutput = styled(Output)<{ isNegative?: boolean }>` - font-size: 2.25rem; - font-weight: var(--fontWeight-bold); - - color: var(--output-sign-color); - --secondary-item-color: currentColor; - --output-sign-color: ${({ isNegative }) => - isNegative !== undefined - ? isNegative - ? `var(--color-negative)` - : `var(--color-positive)` - : `var(--color-text-1)`}; -`;