From 39d79016a73ff2bfae4aef6e004d17ff3acc4371 Mon Sep 17 00:00:00 2001 From: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> Date: Mon, 8 Jul 2024 19:26:49 +0200 Subject: [PATCH] feat: Data room landing page (#387) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: create dataroom page and sections placeholders (#386) * feat: section Dune links (#389) * feat: Dune boards * styles: ExternalLinkCard * styles: sync card hover transitions * styles: reorder CSS properties * styles: mobile responsiveness * feat: External Reports section (#390) * fix: card link height * feat: External reports * feat: move DataRoom content to JSON file * feat: Links component (#391) * feat: LinksWrapper - Dune dashboard * add Social icons * Add Cryptopunks Component (#395) * feat: 🎸 Add Cryptopunks Component * refactor: 💡 Change inline calculations to functions * Update function to const Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> * fix: 🐛 updates as PR comments * refactor: 💡 update css class names to be lowercase * refactor: 💡 update const names as per PR comments --------- Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> * fix: use SVG import (#396) * Add USDCStorage Section With MatterJS (#392) * feat: 🎸 Add USDCStorage Section With MatterJS * fix: 🐛 Overflow Bug * refactor: 💡 remove redundant function * refactor: 💡 Update comments and remove dead code * refactor: 💡 update based on PR comments * refactor: 💡 Updates based on PR comments * refactor: 💡 extract functions to MatterJSUtils * Add Volume Transferred Component (#393) * feat: 🎸 Add Component Volume Transferred * fix: 🐛 updates based on PR comments * Update src/components/DataRoom/VolumeTransferred/styles.module.css --------- Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> * Add Transactions On Chain Component (#394) * feat: 🎸 Add Transactions On Chain Component * refactor: 💡 update as per PR comments * refactor: 💡 updates based on PR comments * fix: lint * Add mobile support for cryptopunks component (#402) * feat: 🎸 add mobile support for cryptopunks component * fix: 🐛 update breakpoint * fix: 🐛 update breakpoint * fix: 🐛 updates based on PR comments * fix: 🐛 update line-height to use px * Update isMobile Hook Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> * fix: 🐛 update import for useIsMediumScreen --------- Co-authored-by: Diogo Soares <32431609+DiogoSoaress@users.noreply.github.com> * Add mobile support for volume transferred component (#400) * feat: 🎸 add mobile support for volume transferred component * fix: 🐛 update breakpoint * style: 💄 reduce font size for volume amount * Add mobile support for transactions on chain component (#401) * feat: 🎸 add mobile support for transactions on chain component * fix: 🐛 update breakpoint * fix: 🐛 update breakpoint to use useIsMediumScreen * fix: 🐛 update consts * Add mobile layout for USDC storage component (#403) * feat: 🎸 Add mobile layout for USDC storage component * fix: 🐛 update based on PR comments * refactor: 💡 separate out utils * fix: 🐛 imports * fix: remove sections that won't make it to v1 * fix: use flex- prefix in CSS * chore: replace logos for SVGs * refactor: delete redundant component * refactor: move utils functions inside a /utils folder * chore: update meta data * chore: remove unused section component * Apply suggestions from code review Co-authored-by: Aaron Cook * fix: use consts for thresholds * fix: address PR comments --------- Co-authored-by: Malay Vasa Co-authored-by: Aaron Cook --- package.json | 2 + public/images/1kx-logo.svg | 10 ++ .../images/DataRoom/cryptopunk-silhouette.svg | 24 +++ public/images/Dataroom/USDC.png | Bin 0 -> 29311 bytes public/images/dune-icon-name-logo.png | Bin 0 -> 2250 bytes public/images/external reports.svg | 9 ++ public/images/external-link.svg | 4 + public/images/green-field-logo.svg | 27 ++++ public/images/messari-logo.svg | 12 ++ public/images/nansen-logo.png | Bin 0 -> 5846 bytes src/components/DataRoom/CryptoPunks/index.tsx | 133 +++++++++++++++++ .../DataRoom/CryptoPunks/styles.module.css | 137 ++++++++++++++++++ .../CryptoPunks/utils/getRandomColor.ts | 21 +++ .../DataRoom/ExternalLinksGrid/index.tsx | 27 ++++ .../ExternalLinksGrid/styles.module.css | 9 ++ src/components/DataRoom/Hero/index.tsx | 13 ++ .../DataRoom/Hero/styles.module.css | 0 .../DataRoom/LinksWrapper/index.tsx | 19 +++ .../DataRoom/LinksWrapper/styles.module.css | 10 ++ .../DataRoom/TransactionsOnChain/Counter.tsx | 70 +++++++++ .../DataRoom/TransactionsOnChain/index.tsx | 67 +++++++++ .../TransactionsOnChain/styles.module.css | 132 +++++++++++++++++ .../TransactionsOnChain/useCounterScroll.ts | 32 ++++ .../utils/calculateYPosition.ts | 19 +++ .../DataRoom/USDCStorage/MatterCanvas.tsx | 127 ++++++++++++++++ .../DataRoom/USDCStorage/MotionTypography.tsx | 31 ++++ src/components/DataRoom/USDCStorage/index.tsx | 75 ++++++++++ .../DataRoom/USDCStorage/styles.module.css | 61 ++++++++ .../USDCStorage/utils/addWallsToWorld.ts | 29 ++++ .../DataRoom/USDCStorage/utils/createCoin.ts | 22 +++ .../DataRoom/USDCStorage/utils/types.ts | 4 + .../DataRoom/VolumeTransferred/index.tsx | 63 ++++++++ .../VolumeTransferred/styles.module.css | 67 +++++++++ src/components/DataRoom/index.tsx | 4 + .../common/Cards/ExternalLinkCard/index.tsx | 25 ++++ .../Cards/ExternalLinkCard/styles.module.css | 54 +++++++ src/content/dataroom.json | 134 +++++++++++++++++ src/pages/dataroom.tsx | 8 + yarn.lock | 10 ++ 39 files changed, 1491 insertions(+) create mode 100644 public/images/1kx-logo.svg create mode 100644 public/images/DataRoom/cryptopunk-silhouette.svg create mode 100644 public/images/Dataroom/USDC.png create mode 100644 public/images/dune-icon-name-logo.png create mode 100644 public/images/external reports.svg create mode 100644 public/images/external-link.svg create mode 100644 public/images/green-field-logo.svg create mode 100644 public/images/messari-logo.svg create mode 100644 public/images/nansen-logo.png create mode 100644 src/components/DataRoom/CryptoPunks/index.tsx create mode 100644 src/components/DataRoom/CryptoPunks/styles.module.css create mode 100644 src/components/DataRoom/CryptoPunks/utils/getRandomColor.ts create mode 100644 src/components/DataRoom/ExternalLinksGrid/index.tsx create mode 100644 src/components/DataRoom/ExternalLinksGrid/styles.module.css create mode 100644 src/components/DataRoom/Hero/index.tsx create mode 100644 src/components/DataRoom/Hero/styles.module.css create mode 100644 src/components/DataRoom/LinksWrapper/index.tsx create mode 100644 src/components/DataRoom/LinksWrapper/styles.module.css create mode 100644 src/components/DataRoom/TransactionsOnChain/Counter.tsx create mode 100644 src/components/DataRoom/TransactionsOnChain/index.tsx create mode 100644 src/components/DataRoom/TransactionsOnChain/styles.module.css create mode 100644 src/components/DataRoom/TransactionsOnChain/useCounterScroll.ts create mode 100644 src/components/DataRoom/TransactionsOnChain/utils/calculateYPosition.ts create mode 100644 src/components/DataRoom/USDCStorage/MatterCanvas.tsx create mode 100644 src/components/DataRoom/USDCStorage/MotionTypography.tsx create mode 100644 src/components/DataRoom/USDCStorage/index.tsx create mode 100644 src/components/DataRoom/USDCStorage/styles.module.css create mode 100644 src/components/DataRoom/USDCStorage/utils/addWallsToWorld.ts create mode 100644 src/components/DataRoom/USDCStorage/utils/createCoin.ts create mode 100644 src/components/DataRoom/USDCStorage/utils/types.ts create mode 100644 src/components/DataRoom/VolumeTransferred/index.tsx create mode 100644 src/components/DataRoom/VolumeTransferred/styles.module.css create mode 100644 src/components/DataRoom/index.tsx create mode 100644 src/components/common/Cards/ExternalLinkCard/index.tsx create mode 100644 src/components/common/Cards/ExternalLinkCard/styles.module.css create mode 100644 src/content/dataroom.json create mode 100644 src/pages/dataroom.tsx diff --git a/package.json b/package.json index dfa601d97..63b47a0cd 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "fuse.js": "^6.6.2", "isomorphic-dompurify": "^1.0.0", "lodash": "^4.17.21", + "matter-js": "^0.20.0", "next": "13.1.5", "react": "18.2.0", "react-dom": "18.2.0", @@ -46,6 +47,7 @@ "@types/dompurify": "^2.4.0", "@types/jest": "^29.5.12", "@types/lodash": "^4.14.191", + "@types/matter-js": "^0.19.6", "@types/node": "18.11.18", "@types/react": "18.0.27", "@types/react-dom": "18.0.10", diff --git a/public/images/1kx-logo.svg b/public/images/1kx-logo.svg new file mode 100644 index 000000000..ebb5cf10e --- /dev/null +++ b/public/images/1kx-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/images/DataRoom/cryptopunk-silhouette.svg b/public/images/DataRoom/cryptopunk-silhouette.svg new file mode 100644 index 000000000..02a23904d --- /dev/null +++ b/public/images/DataRoom/cryptopunk-silhouette.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/public/images/Dataroom/USDC.png b/public/images/Dataroom/USDC.png new file mode 100644 index 0000000000000000000000000000000000000000..66b83629d2f6de6f97a182ccf9a7af69d5e44a60 GIT binary patch literal 29311 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 literal 0 HcmV?d00001 diff --git a/public/images/dune-icon-name-logo.png b/public/images/dune-icon-name-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..363ffcf72d4064d5b7df7120fdd7bf9af179fe3a GIT binary patch literal 2250 zcmbtVdpMKrAD=l!E49fvQ(kR1Re%A}YK&6iO1E95a+x z=)@MqGK|P!&dZWg4(om5_jA4fzt43)-{*Vy+@J5~dtdi;JxL@-0z_I#8UzACY;7zl zz?T8MI4M!U(s}0$Kp?OpiRfYlgunjV!y{vKw1U<-fRIPg2SOeNvz}ac)=I0@HOy~2 zMAZBj05i6_eLb`0Xd{Av1^7CvcD>3Y8}OV^YHBCe^Q!`WOVZY&0kE-E8Dq}+003Yf zCF>(JgGb01Z7u335rsemX=ntDt_cQ`|L?NSzt$AFI^ei7W(~Pk1sDKlgrYk^(c_Wz zwGp&2=ieg(0>*$v0a^r&Qw#tY3PBqs>8|ZUAwn?7Ky3sSUP8mUPG&hVAJDPVOFG@{<002rWcC+-EpQD=;&;o&vTwW9_G)I$Vfb^My`%=(;+ zPzb8Eu8)_YN5mfIJUpR|xTnj}WQb%y((|q%;Bcs8ZWvFh$sxKaDS4kmp@mhYH9mB= zYsP-NVoS>`oSipez8<(7S*IwD&LD-v1+$v&GcPW0)<-P8yDcWg*)&!o@$( zNDYN8GN1eVn(Ck3y+Ef&ojn!kbHv@=+<52KsmzSjn8@HD3Td~oAsjyTv8&}#s@Fj~ zYx5o3Rrv2%uj&}p#kazIb<_uk!w?{lc&n`?-o<~QEyvFTikGD)!ZU4*L@30ayS$a) zP4Ng-E0Ia~9Xc!k{Cyy5c4aG0gj`YM<$ofRp7z(@_R(Rp#UD2;2?qLSW+dQPIW!}7 zv7R4^e&PQui+VL5rN)wo&G^{)Av^1pSb6dH+Q(oqBBL#Ik9`vgUs$Hvv%#W>CpZm( zs$3i}kc#ttTX3wIVvMLyaWitywwah_;SZ%4Z+Fq((s8gCXQNWm_Gd}SUftWr59Pmk zpWcPY1_xL8D@M*n#*X$K@6cCyZ!Z6`HzO~RGe02-7rGc39PT}7APhIS?T5|i28WIj zug5)OycYSw!xbhB;W-K~uV?T#>Y>iS5&6##m2qzdpLr1j?`sx}41BTSin%&zzsxsu zH>;LY@8tiKPr^em#rZ>*g~xqq!$+{vnAeQFp|&AW_3D$I*O)3h8k)o_c>bm_{0DQ_ zxY{GundZ|WP7J}V!K~4>RC002>lzisCfRlK0U>zfZWGToL|p0(Dnp`O8nlonqElHZG8g0_^F!eB@#i{vTv!A`AaC!M?zdd63HhR1e zJuu{+98VQ>sbBgY5jdRM{Z3tpKYD7$uaV#(?174vzt?$wD=%s(pkKMsT!~)^Icq2A zG&!Qa@QoJ_17l0rW(y~k*eZ?T(zHoIq!}n8fR(%twUi}}i?|;7YnnCxq4o^&F^ns& zsG#Wip)|vpdLS?UrDy;%Id`JBjIGuYv4M$(adl_vkq_moA8(lF>IhuX7jR6q_~7uW zlU~L3>i1(WaW&QhOvr;+AlFu$@)=(CAYzxtqJGU zt0CXT)Wr4dqgo(d#&*&YeIZx2Xw41hws0vk^uAn5rBiy@E~Q=qma|>EW3VB5aRR~` zOS{IMhS-`v?UXSLy}{WZ(|c$LFH^1jGLTxTRcTW4#WXMQh{&^OAGa$6zrQXnUU)IR z=hoNS-C(Qn3xSdajvK|QH;rGMOqla~~8t$T}YL>xceOk!7z;TD3-Bx=H@^<{h0YNMW;WYCotoolGwB z%U7w|FX_OS=^o#Fb`zzuR~o-%#j0U3NLy?N8kz&uczSnP4BM?YN46WV*loH^v?=Mb zeB|@KE}9MN?IRK-3HE{OkEx$uYqOv?t>F#1b-!p5S5G*?@bwX)O{|W+pG%H^4lax5; zP!7FURpajMs%kTRKOvDBlU#Uj!9_ + + + + + + + + diff --git a/public/images/external-link.svg b/public/images/external-link.svg new file mode 100644 index 000000000..814626cef --- /dev/null +++ b/public/images/external-link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/images/green-field-logo.svg b/public/images/green-field-logo.svg new file mode 100644 index 000000000..d79113994 --- /dev/null +++ b/public/images/green-field-logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/messari-logo.svg b/public/images/messari-logo.svg new file mode 100644 index 000000000..5b95436b7 --- /dev/null +++ b/public/images/messari-logo.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/nansen-logo.png b/public/images/nansen-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..ddd0e97ebae50eee76e384c03ec916db1fd76053 GIT binary patch literal 5846 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%0DY3)ZaR^LC%+i)+QoQ_jItI;$RBe zze?&170_(!gp)gW{YIS!BW%5YrTqJVD_W|Py8NIs+`kezP)!L?lqnm9x}LzOPU3}@ z%L-H|UR|O;>dUydW`BK1p@^k zQAyYzX|BWvy<&%6Poj-1xuMLFR{qQ&wqh6M z${jX-Z#qxZhzufXRSevxVbaZ{@=~QWf{sICS4c(a{}P_Y><>h9{Vp=#y4FDmEMXnWy#4TwpK*N86sPJD?5DT@$#1 zRjTU*ai|#l@N#KZ_=ZL;e!tH#q*7GZ15)ZJJ~DgLYt~~0H$qS82O{ySkZ0#9MW>KC zI5^Y|(r-f1?W?4CBNN!U=B=$kX7rT(!N^b^3>>baZxA)qdd!}KgF|hRfggnm$FbZJ zB7}J-ot^?ae=|>I3ZB69Je0o|i-Uth-68`*kuz=A9$oZJPwfGn1c*V)Oc0jf*&3Zf zCm}U*l{h#!)Ge~v|E@dQ@n>-03hcvbAUKPapha|~;<0z(16W=Gk@ldiz(`BV#_Uk4KuBIPAWIgF{VFxfp% { + const backgroundRef = useRef(null) + const isMobile = useIsMediumScreen() + const { scrollYProgress } = useScroll({ + target: backgroundRef, + offset: ['start end', 'end start'], + }) + + return ( +

+
+ + + + + {text} + + + {title} + +
+ + {CRYPTOPUNKS_PERCENTAGE} + + + + {CRYPTOPUNKS_FRACTION} + +
+ + {link && } +
+
+
+ ) +} + +const LeftPanel = ({ scrollYProgress, isMobile }: { scrollYProgress: MotionValue; isMobile: boolean }) => { + const translateParams = isMobile ? [0, 1] : [0.25, 0.75] + const opacity = useTransform(scrollYProgress, [0, 0.25, 0.7, 0.75], [0, 1, 1, 0]) + const translateLTR = useTransform(scrollYProgress, translateParams, ['-50%', '0%']) + const translateRTL = useTransform(scrollYProgress, translateParams, ['0%', '-50%']) + + const translateDirection = (index: number) => (index % 2 === 1 ? translateLTR : translateRTL) + + return ( + + {Array.from({ length: CRYPTOPUNK_ROWS_NR }).map((_, outerIndex) => ( + + {Array.from({ length: CRYPTOPUNK_COLUMNS_NR }).map((_, innerIndex) => ( + + + + ))} + + ))} + + ) +} + +const RightPanel = ({ + scrollYProgress, + children, + isMobile, +}: { + scrollYProgress: MotionValue + children: ReactNode + isMobile: boolean +}) => { + const opacityParams = isMobile ? [0.4, 0.45, 0.65, 0.66] : [0.25, 0.35, 0.65, 0.7] + const translateParams = isMobile ? [0.4, 0.45, 0.65, 0.7] : [0.25, 0.35, 0.65, 0.75] + const opacity = useTransform(scrollYProgress, opacityParams, [0, 1, 1, 0]) + const bgTranslate = useTransform(scrollYProgress, translateParams, ['100%', '0%', '0%', '100%']) + + return ( +
+ + {children} + + +
+ ) +} + +export default CryptoPunks diff --git a/src/components/DataRoom/CryptoPunks/styles.module.css b/src/components/DataRoom/CryptoPunks/styles.module.css new file mode 100644 index 000000000..d8fc804af --- /dev/null +++ b/src/components/DataRoom/CryptoPunks/styles.module.css @@ -0,0 +1,137 @@ +.sectionContainer { + height: 300vh; + display: flex; +} + +.stickyContainer { + position: sticky; + top: 0; + width: 100%; + height: 100dvh; + display: flex; +} + +.rightPanelContainer { + width: 50%; + height: 100%; + position: absolute; + right: 0; + color: var(--mui-palette-text-dark); + overflow-x: hidden; + display: flex; + align-items: flex-end; + padding-top: 64px; +} + +.rightPanelContent { + z-index: 20; + display: flex; + flex-direction: column; + width: 100%; + padding: 64px; + height: 100%; + gap: 30px; +} + +.rightPanelBG { + position: absolute; + inset: 0; + background-color: var(--mui-palette-primary-main); + z-index: 10; + margin-top: 72px; +} + +.percentage { + font-size: 120px; + line-height: 120px; +} + +.text { + font-size: 16px; + line-height: 24px; + align-self: flex-start; + margin-bottom: auto; +} + +.fraction { + font-size: 32px; + line-height: 32px; +} + +.statsContainer { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: baseline; +} + +.leftPanelContainer { + width: 100%; + height: 100%; + overflow: hidden; + color: var(--mui-palette-border-light); + display: flex; + flex-direction: column; + justify-content: space-around; + gap: 12px; + padding-top: 80px; +} + +.cryptopunk { + width: 80px; +} + +.cryptoPunkColumns { + display: flex; + gap: 24px; +} + +@media (max-width: 900px) { + .sectionContainer { + height: 400dvh; + } + .stickyContainer { + flex-direction: column-reverse; + } + + .leftPanelContainer { + padding-top: 72px; + width: 100%; + height: 100%; + } + + .rightPanelContainer { + width: 100%; + height: 100%; + position: absolute; + bottom: 0; + } + + .rightPanelContent { + justify-content: end; + padding: 30px; + gap: 30px; + } + + .rightPanelBG { + margin-top: 64px; + } + + .statsContainer { + display: flex; + flex-direction: column; + gap: 30px; + } + + .leftPanelContainer { + overflow: hidden; + } + + .cryptopunk { + width: 60px; + } + + .cryptoPunkColumns { + gap: 12px; + } +} diff --git a/src/components/DataRoom/CryptoPunks/utils/getRandomColor.ts b/src/components/DataRoom/CryptoPunks/utils/getRandomColor.ts new file mode 100644 index 000000000..a6f64f9de --- /dev/null +++ b/src/components/DataRoom/CryptoPunks/utils/getRandomColor.ts @@ -0,0 +1,21 @@ +/** + * Generates a random color code based on predefined probabilities. + * + * The function selects a color based on a random chance. The colors and their associated + * probabilities are as follows: + * - Green (#12FF80) with a 14% chance, + * - Dark Green (#12A154) with a 3% chance, + * - Darker Green (#124228) with a 3% chance. + * If none of these conditions are met, it defaults to the 'currentColor'. + * + * @returns {string} A string representing the color code or 'currentColor'. + */ +export function getRandomColor() { + const chance = Math.random() + + if (chance <= 0.24) return '#12FF80' // Green (14% CHANCE) + if (chance <= 0.17) return '#12A154' // Dark Green (3% CHANCE) + if (chance <= 0.2) return '#124228' // Darker Green (3% CHANCE) + + return 'currentColor' +} diff --git a/src/components/DataRoom/ExternalLinksGrid/index.tsx b/src/components/DataRoom/ExternalLinksGrid/index.tsx new file mode 100644 index 000000000..38044671e --- /dev/null +++ b/src/components/DataRoom/ExternalLinksGrid/index.tsx @@ -0,0 +1,27 @@ +import { Grid, Typography } from '@mui/material' +import ExternalLinkCard from '@/components/common/Cards/ExternalLinkCard' +import { type BaseBlock } from '@/components/Home/types' +import layoutCss from '@/components/common/styles.module.css' +import css from './styles.module.css' + +const ExternalLinksGrid = ({ title, items }: Pick) => ( + + + {title} + + + + {items?.map((item) => { + const { title = '', image, link } = item + + return ( + + + + ) + })} + + +) + +export default ExternalLinksGrid diff --git a/src/components/DataRoom/ExternalLinksGrid/styles.module.css b/src/components/DataRoom/ExternalLinksGrid/styles.module.css new file mode 100644 index 000000000..b23d2e1e1 --- /dev/null +++ b/src/components/DataRoom/ExternalLinksGrid/styles.module.css @@ -0,0 +1,9 @@ +.container { + padding-inline: 28px; +} + +@media (max-width: 900px) { + .title { + margin-bottom: 50px; + } +} diff --git a/src/components/DataRoom/Hero/index.tsx b/src/components/DataRoom/Hero/index.tsx new file mode 100644 index 000000000..fa8cb3037 --- /dev/null +++ b/src/components/DataRoom/Hero/index.tsx @@ -0,0 +1,13 @@ +import { Box, Container, Typography } from '@mui/material' +import type { BaseBlock } from '@/components/Home/types' + +const Hero = ({ title, text }: BaseBlock) => ( + + + {title} + {text} + + +) + +export default Hero diff --git a/src/components/DataRoom/Hero/styles.module.css b/src/components/DataRoom/Hero/styles.module.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/components/DataRoom/LinksWrapper/index.tsx b/src/components/DataRoom/LinksWrapper/index.tsx new file mode 100644 index 000000000..b9463a908 --- /dev/null +++ b/src/components/DataRoom/LinksWrapper/index.tsx @@ -0,0 +1,19 @@ +import Socials from '@/components/Blog/Socials' +import LinkButton from '@/components/common/LinkButton' +import SafeLink from '@/components/common/SafeLink' +import type { Link } from '@/components/Home/types' +import css from './styles.module.css' + +// TODO: define the content to display in the Socials component buttons +const LinksWrapper = ({ title, href }: Link) => { + return ( +
+ + {title} + + +
+ ) +} + +export default LinksWrapper diff --git a/src/components/DataRoom/LinksWrapper/styles.module.css b/src/components/DataRoom/LinksWrapper/styles.module.css new file mode 100644 index 000000000..d4123fd51 --- /dev/null +++ b/src/components/DataRoom/LinksWrapper/styles.module.css @@ -0,0 +1,10 @@ +.wrapper { + display: flex; + flex-direction: row; + gap: 24px; + align-items: center; +} + +.wrapper .socials { + height: fit-content; +} diff --git a/src/components/DataRoom/TransactionsOnChain/Counter.tsx b/src/components/DataRoom/TransactionsOnChain/Counter.tsx new file mode 100644 index 000000000..9e50cf412 --- /dev/null +++ b/src/components/DataRoom/TransactionsOnChain/Counter.tsx @@ -0,0 +1,70 @@ +import { motion, type MotionValue, useSpring, useTransform } from 'framer-motion' +import { useEffect } from 'react' +import { calculateYPosition } from '@/components/DataRoom/TransactionsOnChain/utils/calculateYPosition' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' +import css from './styles.module.css' + +type CounterProps = { + value: number +} + +type DigitProps = { + place: number + value: number +} + +type NumberProps = { + mv: MotionValue + number: number +} + +const DIGIT_HEIGHT_SM = 120 +const DIGIT_HEIGHT_MD = 215 + +const Counter = ({ value }: CounterProps) => { + const integerPart = Math.floor(value) + const decimalPart = value % 1 + + return ( +
+ + . + + + % +
+ ) +} + +const Digit = ({ place, value }: DigitProps) => { + const valueRoundedToPlace = Math.floor(value / place) % 10 + const animatedValue = useSpring(valueRoundedToPlace, { + damping: 5, + stiffness: 15, + }) + + useEffect(() => { + animatedValue.set(valueRoundedToPlace) + }, [animatedValue, valueRoundedToPlace]) + + return ( +
+ {Array.from({ length: 10 }, (_, i) => ( + + ))} +
+ ) +} + +const Number = ({ mv, number }: NumberProps) => { + const height = useIsMediumScreen() ? DIGIT_HEIGHT_SM : DIGIT_HEIGHT_MD + const yPosition = useTransform(mv, (latest: number) => calculateYPosition(latest, number, height)) + + return ( + + {number} + + ) +} + +export default Counter diff --git a/src/components/DataRoom/TransactionsOnChain/index.tsx b/src/components/DataRoom/TransactionsOnChain/index.tsx new file mode 100644 index 000000000..a380ff767 --- /dev/null +++ b/src/components/DataRoom/TransactionsOnChain/index.tsx @@ -0,0 +1,67 @@ +import { Typography } from '@mui/material' +import type { BaseBlock } from '@/components/Home/types' +import Counter from './Counter' +import { useScroll, useTransform, motion } from 'framer-motion' +import React, { useRef } from 'react' +import css from './styles.module.css' +import { useCounterScroll } from './useCounterScroll' +import LinksWrapper from '../LinksWrapper' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' + +// Will be replaced with the actual value in a future PR +const TRANSACTIONS_AMOUNT = 1.75 + +const TransactionsOnChain = ({ text, link }: BaseBlock) => { + return ( +
+
+ + +
+ {text} + +
{link && }
+
+
+
+ ) +} + +const CounterContainer = ({ percentage }: { percentage: number }) => { + const targetScrollRef = useRef(null) + + const { scrollYProgress } = useScroll({ + target: targetScrollRef, + offset: ['start end', 'end start'], + }) + + const isMobile = useIsMediumScreen() + const opacity = useTransform(scrollYProgress, [0, 0.5], [0.1, 1]) + const yTransform = useTransform(scrollYProgress, [0, 0.5], isMobile ? [200, 0] : [600, 0]) + const value = useCounterScroll(scrollYProgress, percentage) + + return ( + +
+ +
+ +
+ +
+ +
+ +
+
+ ) +} + +export default TransactionsOnChain diff --git a/src/components/DataRoom/TransactionsOnChain/styles.module.css b/src/components/DataRoom/TransactionsOnChain/styles.module.css new file mode 100644 index 000000000..ac9b85a34 --- /dev/null +++ b/src/components/DataRoom/TransactionsOnChain/styles.module.css @@ -0,0 +1,132 @@ +.sectionContainer { + height: 150vh; +} + +.stickyContainer { + height: 100vh; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-start; + padding-left: 30px; + position: sticky; + top: 0; +} + +.content { + display: flex; + flex-direction: column; + width: 100%; + align-items: flex-start; + gap: 30px; + margin-top: 80px; + margin-bottom: 80px; +} + +.linkContainer { + align-self: auto; + margin-top: 0; + margin-right: 0; +} + +.counterContainer { + color: var(--mui-palette-primary-main); + font-size: 30px; + font-weight: 400; + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 30px; +} + +.box1 { + height: 50px; + overflow: hidden; +} + +.box2 { + height: 70px; + overflow: hidden; +} + +.digit { + font-variant-numeric: tabular-nums; + position: relative; + width: 0.8ch; + height: 120px; +} + +.number { + position: absolute; + inset: 0px; + display: flex; + align-items: center; + justify-content: center; +} + +.counter { + display: flex; + overflow: hidden; + font-size: 120px; +} + +.counterSpan { + margin-top: 44px; +} + +@media (min-width: 900px) { + .sectionContainer { + height: 200vh; + } + + .content { + width: auto; + margin-top: 80px; + align-items: flex-start; + } + + .digit { + width: 1ch; + } + + .linkContainer { + align-self: flex-end; + margin-top: 80px; + margin-right: 80px; + } + + .counterContainer { + gap: 0; + } + + .box1 { + height: 100px; + } + + .box2 { + height: 150px; + } + + .stickyContainer { + flex-direction: row; + justify-content: space-around; + align-items: center; + gap: 120px; + } + + .counterContainer { + translate: 50px 0; + } + + .digit { + height: 215px; + } + + .counter { + font-size: 200px; + } + + .counterSpan { + margin-top: 96px; + } +} diff --git a/src/components/DataRoom/TransactionsOnChain/useCounterScroll.ts b/src/components/DataRoom/TransactionsOnChain/useCounterScroll.ts new file mode 100644 index 000000000..bb96a6a28 --- /dev/null +++ b/src/components/DataRoom/TransactionsOnChain/useCounterScroll.ts @@ -0,0 +1,32 @@ +import type { MotionValue } from 'framer-motion' +import { useEffect, useState } from 'react' + +// Thresholds for scroll progress +const SCROLL_THRESHOLD_UPPER = 0.21 +const SCROLL_THRESHOLD_LOWER = 0.2 +const INITIAL_VALUE = 0 + +// Custom hook for managing counter based on scroll +export function useCounterScroll(scrollYProgress: MotionValue, percentage: number): number { + const [value, setValue] = useState(0) + + useEffect(() => { + const checkScrollProgress = () => { + const latest = scrollYProgress.get() + if (latest >= SCROLL_THRESHOLD_UPPER) { + setValue(percentage) + } else if (latest <= SCROLL_THRESHOLD_LOWER) { + setValue(INITIAL_VALUE) + } + } + + const unsubscribe = scrollYProgress.on('change', checkScrollProgress) + + // Initial check + checkScrollProgress() + + return () => unsubscribe() + }, [scrollYProgress, percentage]) + + return value +} diff --git a/src/components/DataRoom/TransactionsOnChain/utils/calculateYPosition.ts b/src/components/DataRoom/TransactionsOnChain/utils/calculateYPosition.ts new file mode 100644 index 000000000..e52da9708 --- /dev/null +++ b/src/components/DataRoom/TransactionsOnChain/utils/calculateYPosition.ts @@ -0,0 +1,19 @@ +/** + * Calculates the Y position for individual digits in the counter animation + * to create a smooth rolling effect from 0 to the targetNumber. + * + * @returns {number} The calculated Y position. + */ +export function calculateYPosition(latest: number, targetNumber: number, digitHeight: number): number { + const currentDigit = latest % 10 + const digitDifference = (10 + targetNumber - currentDigit) % 10 + + let yOffset = digitDifference * digitHeight + + // Adjust for shortest path (up or down) + if (digitDifference > 5) { + yOffset -= 10 * digitHeight + } + + return yOffset +} diff --git a/src/components/DataRoom/USDCStorage/MatterCanvas.tsx b/src/components/DataRoom/USDCStorage/MatterCanvas.tsx new file mode 100644 index 000000000..b528fc825 --- /dev/null +++ b/src/components/DataRoom/USDCStorage/MatterCanvas.tsx @@ -0,0 +1,127 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react' +import { Engine, Render, Runner, World } from 'matter-js' +import { debounce } from 'lodash' +import type { MotionValue } from 'framer-motion' +import type { Dimensions } from './utils/types' +import { addWallsToWorld, createWalls } from './utils/addWallsToWorld' +import { createCoin } from './utils/createCoin' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' + +type MatterCanvasProps = { + imgUrl: string + scrollYProgress: MotionValue +} + +const GRAVITY_Y_MOBILE = 1.5 +const GRAVITY_Y_DESKTOP = 1 + +// TODO: Refactor this component to use the custom hooks and improve readability +export default function MatterCanvas({ imgUrl, scrollYProgress }: MatterCanvasProps) { + const isMobile = useIsMediumScreen() + const canvas = useRef(null) + const worldRef = useRef(null) + const engineRef = useRef(null) + const runnerRef = useRef(null) + const [spawnCoins, setSpawnCoins] = useState(false) + const [dimensions, setDimensions] = useState({ width: 500, height: 500 }) + + const createRenderer = ( + canvas: React.RefObject, + engine: Matter.Engine, + dimensions: Dimensions, + isMobile: boolean, + ) => { + return Render.create({ + canvas: canvas.current!, + engine, + options: { + width: dimensions.width, + height: dimensions.height, + background: isMobile ? '#0000' : '#121312', + wireframes: false, + }, + }) + } + + const spawnCoinsInterval = useCallback( + (engine: Matter.Engine, dimensions: Dimensions, imgUrl: string, spawnCoins: boolean) => { + if (!spawnCoins) return + + const spawnInterval = setInterval(() => { + if (engine?.world.bodies.length >= 20) { + clearInterval(spawnInterval) + return + } + World.add(engine.world, createCoin(dimensions, imgUrl, isMobile)) + }, 250) + }, + [isMobile], + ) + + const createWorld = useCallback(() => { + const engine = Engine.create() + engineRef.current = engine + worldRef.current = engine.world + engine.gravity.y = isMobile ? GRAVITY_Y_MOBILE : GRAVITY_Y_DESKTOP // Speeds Up Coins For Mobile + + const render = createRenderer(canvas, engine, dimensions, isMobile) + const walls = createWalls(dimensions) + addWallsToWorld(engine, walls) + spawnCoinsInterval(engine, dimensions, imgUrl, spawnCoins) + + Render.run(render) + const runner = Runner.create() + Runner.run(runner, engine) + }, [dimensions, imgUrl, spawnCoins, isMobile, spawnCoinsInterval]) + + //useEffect To Restart Canvas On Resize + useEffect(() => { + const handleResize = debounce(() => { + isMobile + ? setDimensions({ width: window.innerWidth, height: window.innerHeight }) + : setDimensions({ width: window.innerWidth / 2, height: window.innerHeight }) + }, 300) + + window.addEventListener('resize', handleResize) + handleResize() + + return () => { + window.removeEventListener('resize', handleResize) + } + }, [isMobile]) + + //useEffect To Start/Restart Canvas When Scroll Into View + useEffect(() => { + if (!scrollYProgress) return + const handleScrollChange = () => { + const latest = scrollYProgress.get() + setSpawnCoins(latest > 0.05) + } + + const unsubscribe = scrollYProgress.onChange(handleScrollChange) + return () => unsubscribe() + }, [scrollYProgress]) + + // useEffect To Create And Clean Up The Matter.js World + useEffect(() => { + const cleanup = () => { + if (runnerRef.current) Runner.stop(runnerRef.current) + if (engineRef.current) Engine.clear(engineRef.current) + } + + cleanup() + createWorld() + + return cleanup + }, [createWorld]) + + return ( +
+ +
+ ) +} diff --git a/src/components/DataRoom/USDCStorage/MotionTypography.tsx b/src/components/DataRoom/USDCStorage/MotionTypography.tsx new file mode 100644 index 000000000..d82f45dec --- /dev/null +++ b/src/components/DataRoom/USDCStorage/MotionTypography.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { motion } from 'framer-motion' + +const MotionTypography = ({ customDelay, children }: { customDelay: number; children: React.ReactNode }) => { + // Define Animation Variants For Content + const variants = { + initial: { + opacity: 0, + y: 50, + }, + animate: (custom: number) => ({ + opacity: 1, + y: 0, + transition: { delay: custom }, + }), + } + + return ( + + {children} + + ) +} + +export default MotionTypography diff --git a/src/components/DataRoom/USDCStorage/index.tsx b/src/components/DataRoom/USDCStorage/index.tsx new file mode 100644 index 000000000..14e8c1e45 --- /dev/null +++ b/src/components/DataRoom/USDCStorage/index.tsx @@ -0,0 +1,75 @@ +import { Typography } from '@mui/material' +import type { BaseBlock } from '@/components/Home/types' +import { motion, useScroll, useTransform } from 'framer-motion' +import MatterCanvas from './MatterCanvas' +import { useRef } from 'react' +import LinksWrapper from '../LinksWrapper' +import css from './styles.module.css' +import MotionTypography from './MotionTypography' +import { useIsMediumScreen } from '@/hooks/useMaxWidth' + +// Will be replaced with the actual value in a future PR +const USDC_PERCENTAGE = '10%' + +const USDCStorage = ({ title, text, link, image }: BaseBlock) => { + const isMobile = useIsMediumScreen() + const containerRef = useRef(null) + + // Initialize Tracking scrollYProgress + const { scrollYProgress } = useScroll({ + target: containerRef, + offset: ['start end', 'end start'], + }) + + // Map Container Scroll To Sticky Section Scroll + const mapYProgress = useTransform(scrollYProgress, [0.25, 0.75], [0, 1]) + + // Transform Properties + const getTransformParams = (customTransform: string[]): string[] => { + return isMobile ? ['0%', '0%'] : customTransform + } + + const xTransformContent = useTransform(mapYProgress, [0.8, 1], getTransformParams(['0%', '-100%'])) + const xTransformCanvas = useTransform(mapYProgress, [0.8, 1], getTransformParams(['0%', '100%'])) + const opacity = useTransform(scrollYProgress, [0.75, 1], [1, 0]) + + return ( +
+ + + + {title} + + + + + {USDC_PERCENTAGE} + + + + {text} + + + {link && } + + + + + + + +
+ ) +} + +export default USDCStorage diff --git a/src/components/DataRoom/USDCStorage/styles.module.css b/src/components/DataRoom/USDCStorage/styles.module.css new file mode 100644 index 000000000..89b1a72fe --- /dev/null +++ b/src/components/DataRoom/USDCStorage/styles.module.css @@ -0,0 +1,61 @@ +.sectionContainer { + height: 300vh; +} + +.stickyContainer { + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + position: sticky; + top: 0; + overflow: hidden; +} + +.usdcPercentage { + font-size: 180px; + line-height: 1; + font-weight: 400; +} + +.text { + margin-bottom: 70px; +} + +.content { + width: 50%; + display: flex; + flex-direction: column; + justify-content: space-between; + height: 100%; + padding: 128px; +} + +.canvas { + width: 50%; + height: 100%; +} + +@media (max-width: 900px) { + .stickyContainer { + padding-top: 72px; + justify-content: flex-start; + flex-direction: column; + } + + .usdcPercentage { + font-size: 120px; + } + + .content { + width: 100%; + padding: 30px; + height: 50%; + } + + .canvas { + width: 100%; + position: absolute; + top: 0; + } +} diff --git a/src/components/DataRoom/USDCStorage/utils/addWallsToWorld.ts b/src/components/DataRoom/USDCStorage/utils/addWallsToWorld.ts new file mode 100644 index 000000000..8a4243934 --- /dev/null +++ b/src/components/DataRoom/USDCStorage/utils/addWallsToWorld.ts @@ -0,0 +1,29 @@ +import { Bodies, World } from 'matter-js' +import type { Dimensions } from './types' + +const WALL_BORDER_WIDTH = 25 +const WALL_LENGTH = 500 + +export const createWalls = (dimensions: Dimensions) => [ + { x: 0, y: -WALL_BORDER_WIDTH / 2, width: WALL_LENGTH * 8, height: WALL_BORDER_WIDTH }, + { + x: 0, + y: dimensions.height + WALL_BORDER_WIDTH / 2 - 15, + width: dimensions.width * 9, + height: WALL_BORDER_WIDTH, + }, + { x: 0, y: dimensions.height / 2, width: WALL_BORDER_WIDTH, height: WALL_LENGTH * 8 }, + { x: dimensions.width, y: dimensions.height / 2, width: WALL_BORDER_WIDTH, height: WALL_LENGTH * 8 }, +] + +export const addWallsToWorld = (engine: Matter.Engine, walls: any[]) => { + World.add( + engine.world, + walls.map((wall) => + Bodies.rectangle(wall.x, wall.y, wall.width, wall.height, { + isStatic: true, + render: { visible: false }, + }), + ), + ) +} diff --git a/src/components/DataRoom/USDCStorage/utils/createCoin.ts b/src/components/DataRoom/USDCStorage/utils/createCoin.ts new file mode 100644 index 000000000..52a298f32 --- /dev/null +++ b/src/components/DataRoom/USDCStorage/utils/createCoin.ts @@ -0,0 +1,22 @@ +import { Bodies } from 'matter-js' +import type { Dimensions } from './types' + +const IMG_TEXTURE_SIZE = 256 // Size Of The USDC.png Image In Pixels +const COIN_RADIUS_SM = 35 +const COIN_RADIUS_MD = 50 + +export const createCoin = (dimensions: Dimensions, imgUrl: string, isMobile: boolean) => { + const coinRadius = isMobile ? COIN_RADIUS_SM : COIN_RADIUS_MD + const IMG_SCALE = (coinRadius * 2) / IMG_TEXTURE_SIZE // Scale Factor To Match Image Texture Size With Coin Radius + + return Bodies.circle(Math.random() * dimensions.width * 0.5 + dimensions.width * 0.25, 0, coinRadius, { + restitution: 0.4, + render: { + sprite: { + texture: imgUrl, + xScale: IMG_SCALE, + yScale: IMG_SCALE, + }, + }, + }) +} diff --git a/src/components/DataRoom/USDCStorage/utils/types.ts b/src/components/DataRoom/USDCStorage/utils/types.ts new file mode 100644 index 000000000..c89199ee2 --- /dev/null +++ b/src/components/DataRoom/USDCStorage/utils/types.ts @@ -0,0 +1,4 @@ +export type Dimensions = { + width: number + height: number +} diff --git a/src/components/DataRoom/VolumeTransferred/index.tsx b/src/components/DataRoom/VolumeTransferred/index.tsx new file mode 100644 index 000000000..9141b9771 --- /dev/null +++ b/src/components/DataRoom/VolumeTransferred/index.tsx @@ -0,0 +1,63 @@ +import { Typography } from '@mui/material' +import { useScroll, useTransform, motion } from 'framer-motion' +import css from './styles.module.css' +import { useRef } from 'react' +import type { BaseBlock } from '@/components/Home/types' +import LinksWrapper from '../LinksWrapper' + +// Will be replaced with the actual value in a future PR +const VOLUME_AMOUNT = '$611,127,712,666' + +const VolumeTransferred = ({ title, text, link }: BaseBlock) => { + const targetRef = useRef(null) + const { scrollYProgress } = useScroll({ + target: targetRef, + offset: ['start end', 'end start'], + }) + + const transformLTR = useTransform(scrollYProgress, [0.25, 0.75], ['-100%', '120%']) + const transformRTL = useTransform(scrollYProgress, [0.25, 0.75], ['120%', '-140%']) + const opacityLTR = useTransform(scrollYProgress, [0.25, 0.3, 0.7, 0.75], [0, 1, 1, 0]) + const opacityRTL = useTransform(scrollYProgress, [0.25, 0.3, 0.7, 0.75], [0, 1, 1, 0]) + + return ( +
+
+ + {title} + + + + + {VOLUME_AMOUNT} + + + + +
+ + {VOLUME_AMOUNT} + + + {text} +
+
+
{link && }
+
+
+ ) +} + +export default VolumeTransferred diff --git a/src/components/DataRoom/VolumeTransferred/styles.module.css b/src/components/DataRoom/VolumeTransferred/styles.module.css new file mode 100644 index 000000000..b9e6afcc6 --- /dev/null +++ b/src/components/DataRoom/VolumeTransferred/styles.module.css @@ -0,0 +1,67 @@ +.sectionContainer { + height: 200vh; +} + +.stickyContainer { + height: 100vh; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + position: sticky; + top: 0; + overflow: hidden; +} + +.headerText { + margin-bottom: 150px; + margin-top: 100px; + margin-left: 50px; +} + +.volumeTrailContainer { + white-space: nowrap; + align-items: baseline; + gap: 24px; + display: flex; +} + +.volume { + color: var(--mui-palette-primary-main); + font-size: 180px; + font-weight: 400; +} + +.slidingModule { + height: 200px; + margin-top: 50px; +} + +.linksContainer { + position: absolute; + top: 100px; + right: 50px; +} + +@media (max-width: 900px) { + .headerText { + margin-bottom: 50px; + margin-left: 30px; + margin-top: 0px; + margin-right: 30px; + } + + .stickyContainer { + justify-content: end; + } + + .linksContainer { + position: inherit; + margin-left: 30px; + margin-bottom: 30px; + } + + .volume { + font-size: 120px; + } +} diff --git a/src/components/DataRoom/index.tsx b/src/components/DataRoom/index.tsx new file mode 100644 index 000000000..468b70f62 --- /dev/null +++ b/src/components/DataRoom/index.tsx @@ -0,0 +1,4 @@ +import PageContent from '@/components/common/PageContent' +import dataroomContent from '@/content/dataroom.json' + +export const DataRoom = () => diff --git a/src/components/common/Cards/ExternalLinkCard/index.tsx b/src/components/common/Cards/ExternalLinkCard/index.tsx new file mode 100644 index 000000000..a34600db6 --- /dev/null +++ b/src/components/common/Cards/ExternalLinkCard/index.tsx @@ -0,0 +1,25 @@ +import { Typography } from '@mui/material' +import LinkButton from '@/components/common/LinkButton' +import { type BaseBlock } from '@/components/Home/types' +import ExternalLinkIcon from '@/public/images/external-link.svg' +import css from './styles.module.css' + +const ExternalLinkCard = ({ title, image, link }: Pick) => ( +
+
{image ? {image.alt} : undefined}
+ +
+ + {title} + +
+ + + + + + +
+) + +export default ExternalLinkCard diff --git a/src/components/common/Cards/ExternalLinkCard/styles.module.css b/src/components/common/Cards/ExternalLinkCard/styles.module.css new file mode 100644 index 000000000..545f2cded --- /dev/null +++ b/src/components/common/Cards/ExternalLinkCard/styles.module.css @@ -0,0 +1,54 @@ +/* Card */ +.card { + position: relative; + padding: 24px; + border-radius: 16px; + height: 191px; + display: flex; + flex-direction: column; + box-shadow: inset 0 0 0 1px var(--mui-palette-border-light); + transition: box-shadow var(--transition-duration); +} + +.cardHeader { + height: 32px; +} + +.cardHeader img { + height: 100%; +} + +.cardBody { + display: flex; + flex-direction: column; + flex-grow: 1; +} + +.title { + margin-top: auto; +} + +.link { + height: 0; +} + +.icon { + position: absolute; + top: 24px; + right: 24px; + width: 24px; + height: 24px; +} + +.icon path { + transition: fill var(--transition-duration); +} + +/* Hover */ +.card:hover { + box-shadow: inset 0 0 0 1px var(--mui-palette-primary-main); +} + +.card:hover .icon path { + fill: var(--mui-palette-primary-main); +} diff --git a/src/content/dataroom.json b/src/content/dataroom.json new file mode 100644 index 000000000..174c68791 --- /dev/null +++ b/src/content/dataroom.json @@ -0,0 +1,134 @@ +[ + { + "pageTitle": "Safe Data Room - Key Insights into the Safe Ecosystem", + "description": "Uncover the impact of the Safe ecosystem in crypto. Explore detailed metrics on USDC storage, CryptoPunks holdings, and transaction volumes. Access comprehensive reports from Dune Analytics, Messari, Nansen, and more.", + "image": "/images/Home/meta-image-homepage.jpg", + "component": "common/MetaTags" + }, + { + "component": "DataRoom/Hero", + "title": "Welcome to Safe Data Room", + "text": "Explore Mind Bending Sats from Safe Universe" + }, + { + "component": "DataRoom/USDCStorage", + "title": "We store...
", + "text": "of all USDC", + "link": { + "title": "Dune dashboard", + "href": "https://dune.com/queries/3710102/6242478" + }, + "image": { + "src": "/images/Dataroom/USDC.png", + "alt": "USDC Coin" + } + }, + { + "component": "DataRoom/CryptoPunks", + "title": "CryptoPunks", + "text": "Some text thats changing
Confirming the sources of the data
Any other info regarding the stats", + "link": { + "title": "Dune dashboard", + "href": "https://dune.com/queries/3710102/6242478" + } + }, + { + "component": "DataRoom/VolumeTransferred", + "title": "with all time volume
transferred", + "text": "and counting...", + "link": { + "title": "Dune dashboard", + "href": "https://dune.com/queries/3710102/6242478" + } + }, + { + "component": "DataRoom/TransactionsOnChain", + "text": "transactions on chain originate from a Safe Account", + "link": { + "title": "Dune dashboard", + "href": "https://dune.com/queries/3710102/6242478" + } + }, + { + "component": "DataRoom/ExternalLinksGrid", + "title": "Dune Boards", + "items": [ + { + "title": "Safe on all chains", + "image": { + "src": "/images/dune-icon-name-logo.png", + "alt": "Dune Analytics logo" + }, + "link": { + "href": "https://dune.com/safe/all" + } + }, + { + "title": "Smart Account landscape", + "image": { + "src": "/images/dune-icon-name-logo.png", + "alt": "Dune Analytics logo" + }, + "link": { + "href": "https://dune.com/safe/smart-accounts" + } + } + ] + }, + { + "component": "DataRoom/ExternalLinksGrid", + "title": "External Reports", + "items": [ + { + "title": "Messari Q4 2023", + "image": { + "src": "/images/messari-logo.svg", + "alt": "Messari logo" + }, + "link": { + "href": "https://messari.io/report/state-of-safe-q1-2024" + } + }, + { + "title": "Messari Q1 2024", + "image": { + "src": "/images/messari-logo.svg", + "alt": "Messari logo" + }, + "link": { + "href": "https://messari.io/report/safe-q3-2023-brief" + } + }, + { + "title": "Nansen Report", + "image": { + "src": "/images/nansen-logo.png", + "alt": "Nansen logo" + }, + "link": { + "href": "https://dune.com/safe/all" + } + }, + { + "title": "Greenfield One Thesis Post", + "image": { + "src": "/images/green-field-logo.svg", + "alt": "Greenfield logo" + }, + "link": { + "href": "https://dune.com/safe/smart-accounts" + } + }, + { + "title": "1kx Thesis Post", + "image": { + "src": "/images/1kx-logo.svg", + "alt": "1kx logo" + }, + "link": { + "href": "https://dune.com/safe/all" + } + } + ] + } +] diff --git a/src/pages/dataroom.tsx b/src/pages/dataroom.tsx new file mode 100644 index 000000000..f12b915d6 --- /dev/null +++ b/src/pages/dataroom.tsx @@ -0,0 +1,8 @@ +import type { NextPage } from 'next' +import { DataRoom } from '@/components/DataRoom' + +const DataRoomPage: NextPage = () => { + return +} + +export default DataRoomPage diff --git a/yarn.lock b/yarn.lock index b3ca4f103..1d8b3155a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2166,6 +2166,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.191.tgz#09511e7f7cba275acd8b419ddac8da9a6a79e2fa" integrity sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ== +"@types/matter-js@^0.19.6": + version "0.19.6" + resolved "https://registry.yarnpkg.com/@types/matter-js/-/matter-js-0.19.6.tgz#2fe5f3702573365272419c69f30c64158c5309ec" + integrity sha512-ffk6tqJM5scla+ThXmnox+mdfCo3qYk6yMjQsNcrbo6eQ5DqorVdtnaL+1agCoYzxUjmHeiNB7poBMAmhuLY7w== + "@types/node@*": version "20.14.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.5.tgz#fe35e3022ebe58b8f201580eb24e1fcfc0f2487d" @@ -5656,6 +5661,11 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" +matter-js@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/matter-js/-/matter-js-0.20.0.tgz#dc3c95c40ea5ef8937788a2770c3f97223b352ed" + integrity sha512-iC9fYR7zVT3HppNnsFsp9XOoQdQN2tUyfaKg4CHLH8bN+j6GT4Gw7IH2rP0tflAebrHFw730RR3DkVSZRX8hwA== + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"