diff --git a/src/components/DataRoom/AssetsSecured/SlidingContent.tsx b/src/components/DataRoom/AssetsSecured/SlidingContent.tsx index 136cb3ef4..026ec6e5b 100644 --- a/src/components/DataRoom/AssetsSecured/SlidingContent.tsx +++ b/src/components/DataRoom/AssetsSecured/SlidingContent.tsx @@ -1,10 +1,7 @@ +import React, { type RefObject } from 'react' import type { BaseBlock } from '@/components/Home/types' -import { useTransform } from 'framer-motion' -import css from './styles.module.css' -import type { RefObject } from 'react' -import { motion } from 'framer-motion' +import { motion, useTransform } from 'framer-motion' import { Typography } from '@mui/material' -import { Cex, type CEX } from './Cex' import { useSafeDataRoomStats } from '@/hooks/useSafeDataRoomStats' import { getNormalizationFactor } from './utils/getNormalizationFactor' import { formatDate } from '@/lib/formatDate' @@ -12,6 +9,8 @@ import { useIsMediumScreen } from '@/hooks/useMaxWidth' import { formatValue } from '@/lib/formatValue' import useContainerSize from '@/hooks/useContainerSize' import useScrollProgress from '@/hooks/useScrollProgress' +import { ComparisonType, TvlComparison, type TvlComparisonProps } from '@/components/DataRoom/TvlComparison' +import css from './styles.module.css' const LAST_UPDATED_FALLBACK = 1722946836.34 const MOBILE_VIEWPORT_FRACTION = 0.8 @@ -22,7 +21,7 @@ export default function SlidingContent({ caption, cexes, containerRef, -}: Omit & { cexes: CEX[]; containerRef: RefObject }) { +}: Omit & { cexes: TvlComparisonProps[]; containerRef: RefObject }) { const { tvlRobinhoodCEX, tvlOKX, tvlBinance, tvlSafe, lastUpdated } = useSafeDataRoomStats() const { width: viewportWidth } = useContainerSize(containerRef) @@ -55,10 +54,10 @@ export default function SlidingContent({ const squareRatio = formatValue(normalizationFactor) - const transformLTR = useTransform(scrollYProgress, [0.5, 0.75], ['0%', '100%']) - const transformRTL = useTransform(scrollYProgress, [0.5, 0.75], ['0', '-100%']) - const opacityLTR = useTransform(scrollYProgress, [0.25, 0.35, 0.5, 0.75], [0, 1, 1, 0]) - const opacityRTL = useTransform(scrollYProgress, [0.1, 0.35, 0.5, 0.75], [0, 1, 1, 0]) + const transformLTR = useTransform(scrollYProgress, [0.5, 1.0], ['0%', '100%']) + const transformRTL = useTransform(scrollYProgress, [0.5, 1.0], ['0', '-100%']) + const opacityLTR = useTransform(scrollYProgress, [0.25, 0.35, 0.5, 1.0], [0, 1, 1, 0]) + const opacityRTL = useTransform(scrollYProgress, [0.1, 0.35, 0.5, 1.0], [0, 1, 1, 0]) return ( <> @@ -72,14 +71,17 @@ export default function SlidingContent({ >
{caption} - 1 square - ${squareRatio} + + 1 square - ${squareRatio} +
{cexes.map((cex, index) => { const tvl = dynamicTvl.find((item) => item.name === cex.name)?.tvl || cex.tvl return ( - import('./SlidingContent')) -const Cexes = ({ title, caption, cexes }: BaseBlock & { cexes: CEX[] }) => { +const Cexes = ({ title, caption, cexes }: BaseBlock & { cexes: TvlComparisonProps[] }) => { const backgroundRef = useRef(null) return ( diff --git a/src/components/DataRoom/AssetsSecured/styles.module.css b/src/components/DataRoom/AssetsSecured/styles.module.css index 70757d6f5..ec9c7338d 100644 --- a/src/components/DataRoom/AssetsSecured/styles.module.css +++ b/src/components/DataRoom/AssetsSecured/styles.module.css @@ -34,44 +34,6 @@ gap: 32px; } -.cexEntry { - display: flex; - flex-direction: row; - align-items: center; - gap: 22px; -} - -.squareGrid { - display: flex; - flex-direction: row; - column-gap: 8px; - align-items: center; -} - -.gridItem { - width: 16px; - height: 16px; - border-radius: 3px; - background-color: var(--mui-palette-primary-light); -} - -.gridItemMain { - background-color: var(--mui-palette-primary-main); -} - -.labelContainer { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - gap: 22px; -} - -.labelCaption { - white-space: nowrap; - color: var(--mui-palette-primary-light); -} - @media (max-width: 1000px) { .sectionContainer { height: 200lvh; @@ -98,20 +60,6 @@ padding-bottom: 12px; } - .labelCaption { - font-size: 18px; - } - - .labelContainer { - gap: 12px; - } - - .cexEntry { - flex-direction: column; - align-items: start; - gap: 8px; - } - .title { margin-top: 40px; text-align: left; diff --git a/src/components/DataRoom/CategoryLeaders/Content.tsx b/src/components/DataRoom/CategoryLeaders/Content.tsx new file mode 100644 index 000000000..6babddcba --- /dev/null +++ b/src/components/DataRoom/CategoryLeaders/Content.tsx @@ -0,0 +1,79 @@ +import type { BaseBlock } from '@/components/Home/types' +import { useTransform } from 'framer-motion' +import css from './styles.module.css' +import type { RefObject } from 'react' +import { motion } from 'framer-motion' +import { Typography } from '@mui/material' +import { ComparisonType, type TvlComparisonProps } from '@/components/DataRoom/TvlComparison' +import { useSafeDataRoomStats } from '@/hooks/useSafeDataRoomStats' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' +import { formatValue } from '@/lib/formatValue' +import useScrollProgress from '@/hooks/useScrollProgress' +import { TvlComparison } from '@/components/DataRoom/TvlComparison' + +export default function Content({ + title, + leaders, + containerRef, +}: Omit & { leaders: TvlComparisonProps[]; containerRef: RefObject }) { + const { tvlLido, tvlEigenLayer, tvlUniswap, tvlAAVE, tvlSafe } = useSafeDataRoomStats() + + const isMobile = useIsMediumScreen() + const { scrollYProgress } = useScrollProgress(containerRef) + + // Create a mapping object for TVL values + const tvlMapping: Record = { + tvlLido, + tvlUniswap, + tvlSafe, + tvlEigenLayer, + tvlAAVE, + } + + // Get the TVL values for each LEADER + const dynamicTvl = leaders.map(({ name, tvl: tvlFallback }) => { + // get the varibale name of the dynamic TVL + const dynamicTvlString = `tvl${name.split(' ')[0]}` + + return { name, tvl: tvlMapping[dynamicTvlString] || tvlFallback } + }) + + const normalizationFactor = 1_000_000_000 // 1 billion + const squareRatio = formatValue(normalizationFactor) + + const opacity = useTransform(scrollYProgress, isMobile ? [0, 0.35, 0.5, 1.0] : [0.1, 0.35, 0.5, 0.75], [0, 1, 1, 0]) + + return ( + + + {title} + + + + 1 square - ${squareRatio} + + +
+ {[[0, 1], [2], [3, 4]].map((column, columnIndex) => ( +
+ {column.map((index) => ( + item.name === leaders[index].name)?.tvl || leaders[index].tvl} + boxColor={leaders[index].boxColor} + name={leaders[index].name} + normalizationFactor={normalizationFactor} + /> + ))} +
+ ))} +
+
+ ) +} diff --git a/src/components/DataRoom/CategoryLeaders/index.tsx b/src/components/DataRoom/CategoryLeaders/index.tsx new file mode 100644 index 000000000..a250a8d8e --- /dev/null +++ b/src/components/DataRoom/CategoryLeaders/index.tsx @@ -0,0 +1,21 @@ +import { useRef } from 'react' +import dynamic from 'next/dynamic' +import type { BaseBlock } from '@/components/Home/types' +import type { TvlComparisonProps } from '@/components/DataRoom/TvlComparison' +import css from './styles.module.css' + +const Content = dynamic(() => import('./Content')) + +const Leaders = ({ title, leaders }: BaseBlock & { leaders: TvlComparisonProps[] }) => { + const backgroundRef = useRef(null) + + return ( +
+
+ +
+
+ ) +} + +export default Leaders diff --git a/src/components/DataRoom/CategoryLeaders/styles.module.css b/src/components/DataRoom/CategoryLeaders/styles.module.css new file mode 100644 index 000000000..00df05d69 --- /dev/null +++ b/src/components/DataRoom/CategoryLeaders/styles.module.css @@ -0,0 +1,79 @@ +.sectionContainer { + height: 200vh; + display: flex; +} + +.stickyContainer { + position: sticky; + top: 0; + width: 100%; + height: 100dvh; + display: flex; + flex-direction: column; + padding: 100px 128px; + overflow: hidden; +} + +.leadersContainer { + position: relative; +} + +.label { + position: absolute; + top: 0; + right: 0; + font-size: 18px; + font-weight: 400; +} + +.columnsGrid { + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 100px; +} + +.leaderColumn { + display: flex; + flex-direction: column; + width: 30%; + gap: 100px; +} + +@media (max-width: 1000px) { + .sectionContainer { + height: 200lvh; + } + + .stickyContainer { + padding: 72px 30px 30px; + height: 100lvh; + justify-content: space-between; + } + + .leadersContainer { + display: flex; + flex-direction: column; + height: 100%; + justify-content: space-between; + } + + .columnsGrid { + flex-direction: column; + margin-top: 12px; + gap: 18px; + } + + .leaderColumn { + width: 100%; + gap: 18px; + justify-items: flex-start; + } + + .title { + margin-top: 0px; + text-align: left; + font-size: 40px; + line-height: 48px; + } +} diff --git a/src/components/DataRoom/AssetsSecured/Cex.tsx b/src/components/DataRoom/TvlComparison/index.tsx similarity index 54% rename from src/components/DataRoom/AssetsSecured/Cex.tsx rename to src/components/DataRoom/TvlComparison/index.tsx index 1edd430ec..559a64abe 100644 --- a/src/components/DataRoom/AssetsSecured/Cex.tsx +++ b/src/components/DataRoom/TvlComparison/index.tsx @@ -3,12 +3,18 @@ import { motion, type MotionProps } from 'framer-motion' import { Typography } from '@mui/material' import { formatValue } from '@/lib/formatValue' -export type CEX = { +export const enum ComparisonType { + CEX = 'CEX', + LEADER = 'Leader', +} + +export type TvlComparisonProps = { name: string boxColor: string tvl: number normalizationFactor: number - date: string + date?: string + type: ComparisonType } const textMotionProps: MotionProps = { @@ -22,23 +28,24 @@ const textMotionProps: MotionProps = { }, } -const gridMotionProps: MotionProps = { +const getGridMotionProps = (type: ComparisonType): MotionProps => ({ initial: { scale: 0, x: -10 }, whileInView: { scale: 1, x: 0 }, viewport: { once: true }, transition: { type: 'spring', - stiffness: 260, - damping: 20, + ...(type === ComparisonType.CEX ? { stiffness: 260, damping: 20 } : { stiffness: 400, damping: 30, mass: 0.8 }), }, -} +}) -export const Cex = ({ boxColor, name, normalizationFactor, tvl, date }: CEX) => { - const boxes = Math.round(tvl / normalizationFactor) +export const TvlComparison = ({ boxColor, name, normalizationFactor, tvl, date, type }: TvlComparisonProps) => { + const boxes = + type === ComparisonType.CEX ? Math.round(tvl / normalizationFactor) : Math.floor(tvl / normalizationFactor) + const gridMotionProps = getGridMotionProps(type) return ( -
-
+
+
{Array.from({ length: boxes }).map((_, index) => (
- + {name} + {type === ComparisonType.LEADER && '\u00A0'} - - {'$' + formatValue(tvl) + ' as of ' + date} + + {type === ComparisonType.CEX ? '$' + formatValue(tvl) + ' as of ' + date : ' - $' + formatValue(tvl)}
diff --git a/src/components/DataRoom/TvlComparison/styles.module.css b/src/components/DataRoom/TvlComparison/styles.module.css new file mode 100644 index 000000000..5c8b8b433 --- /dev/null +++ b/src/components/DataRoom/TvlComparison/styles.module.css @@ -0,0 +1,73 @@ +.inlineLabel { + display: flex; + flex-direction: row; + align-items: baseline; + gap: 22px; +} + +.blockLabel { + display: flex; + flex-direction: column; + justify-items: flex-start; + align-items: flex-start; +} + +.linedItemsGrid { + display: flex; + flex-direction: row; + align-items: center; + column-gap: 8px; +} + +.wrappedItemsGrid { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 16px; + max-width: 304px; +} + +.gridItem { + width: 16px; + height: 16px; + border-radius: 3px; + background-color: var(--mui-palette-primary-light); +} + +.gridItemMain { + background-color: var(--mui-palette-primary-main); +} + +.labelContainer { + display: flex; + flex-direction: row; + align-items: baseline; + justify-content: center; + margin-top: 12px; +} + +.cexTitle { + margin-right: 22px; +} + +.labelCaption { + font-size: 18px; + white-space: nowrap; + color: var(--mui-palette-primary-light); +} + +@media (max-width: 1000px) { + .inlineLabel { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .wrappedItemsGrid { + gap: 8px; + } + + .cexTitle { + font-size: 18px; + } +} diff --git a/src/content/dataroom.json b/src/content/dataroom.json index 5d567a956..de1fb813b 100644 --- a/src/content/dataroom.json +++ b/src/content/dataroom.json @@ -103,6 +103,33 @@ ], "title": "Safe TVL compares and surpasses major CEXs" }, + { + "component": "DataRoom/CategoryLeaders", + "leaders": [ + { + "name": "Lido (Liquid Staking)", + "tvl": 25916636061.2 + }, + { + "name": "Uniswap (Liquidity Pool)", + "tvl": 4774854756.38199 + }, + { + "name": "Safe", + "tvl": 62468973126.503235, + "boxColor": "main" + }, + { + "name": "EigenLayer (Re-Staking)", + "tvl": 12325835638.623302 + }, + { + "name": "AAVE", + "tvl": 12571571373.304926 + } + ], + "title": "Safe TVL
x
TVL of category leaders" + }, { "component": "DataRoom/SeeMore", "text": "Want to see more?" diff --git a/src/contexts/SafeDataRoomContext.ts b/src/contexts/SafeDataRoomContext.ts index 9224309e8..9b4be13d2 100644 --- a/src/contexts/SafeDataRoomContext.ts +++ b/src/contexts/SafeDataRoomContext.ts @@ -10,6 +10,10 @@ type SafeDataRoomStats = { tvlRobinhoodCEX: number | null tvlOKX: number | null tvlBinance: number | null + tvlLido: number | null + tvlUniswap: number | null + tvlEigenLayer: number | null + tvlAAVE: number | null lastUpdated: number | null annualSwapFees: number | null } @@ -24,6 +28,10 @@ const SafeDataRoomContext = createContext({ tvlRobinhoodCEX: null, tvlOKX: null, tvlBinance: null, + tvlLido: null, + tvlUniswap: null, + tvlEigenLayer: null, + tvlAAVE: null, lastUpdated: null, annualSwapFees: null, }) diff --git a/src/pages/dataroom.tsx b/src/pages/dataroom.tsx index 0f19346ae..ad6b5df23 100644 --- a/src/pages/dataroom.tsx +++ b/src/pages/dataroom.tsx @@ -21,6 +21,10 @@ export async function getStaticProps() { cex_tvl_robinhood, cex_tvl_okx, cex_tvl_binance, + defi_tvl_aave, + defi_tvl_eigenlayer, + defi_tvl_lido, + defi_tvl_uniswap, last_updated, annual_swap_fees, } = dataRoomStats @@ -38,6 +42,10 @@ export async function getStaticProps() { tvlRobinhoodCEX: Number(cex_tvl_robinhood), tvlOKX: Number(cex_tvl_okx), tvlBinance: Number(cex_tvl_binance), + tvlLido: Number(defi_tvl_lido), + tvlAAVE: Number(defi_tvl_aave), + tvlEigenLayer: Number(defi_tvl_eigenlayer), + tvlUniswap: Number(defi_tvl_uniswap), lastUpdated: Number(last_updated), annualSwapFees: Number(annual_swap_fees), },