From 99281899b15f15ce253529bcae3e55ccd6c9c114 Mon Sep 17 00:00:00 2001 From: Riyanshu Patro Date: Mon, 17 Aug 2026 16:19:35 +0530 Subject: [PATCH 1/3] feat: new landing page for agentic ai --- docusaurus.config.js | 2 +- src/components/Accordion/index.tsx | 39 +- src/components/Home/AgenticScaleSection.tsx | 335 +++++++ src/components/Home/BuiltToScaleBanner.tsx | 118 +++ src/components/Home/ClosingCTA.tsx | 132 +++ src/components/Home/FeatureCard.tsx | 135 +++ src/components/Home/PaidStatsRow.tsx | 375 ++++++++ src/components/Home/ProblemNarrative.tsx | 359 +++++++ src/components/Home/RecentBlogPosts.js | 4 +- src/components/Home/SolutionPanel.tsx | 117 +++ src/components/Home/UniversalStatCallout.tsx | 142 +++ src/components/InfoBar.tsx | 16 +- src/components/QnA/QnA.tsx | 37 +- src/config/AgentFeaturesListA.ts | 39 + src/config/AgentFeaturesListB.ts | 30 + src/config/FooterList.ts | 13 +- src/config/HomeAgentFAQsList.tsx | 57 ++ src/config/PaidStatsList.ts | 56 ++ src/config/ProblemNarrativeList.ts | 30 + src/css/custom.css | 4 +- src/pages-composition/homeComp.tsx | 905 +++--------------- src/pages/home.tsx | 11 +- src/segments/Footer.tsx | 39 +- src/segments/Header.tsx | 19 +- src/theme/Root.js | 4 +- .../home/built-to-scale/push-logo-mark.svg | 5 + .../home/built-to-scale/ring-pattern.png | Bin 0 -> 305615 bytes .../home/feature-grid-b/universal-access.png | Bin 0 -> 5721 bytes .../feature-grid-b/universal-contracts.png | Bin 0 -> 8661 bytes .../feature-grid-b/universal-read-write.png | Bin 0 -> 4935 bytes .../website/home/paid-stats/arbitrum.svg | 11 + .../assets/website/home/paid-stats/base.svg | 3 + .../website/home/paid-stats/bnb-chain.svg | 11 + .../website/home/paid-stats/ethereum.svg | 13 + .../website/home/paid-stats/glow-ellipse.svg | 12 + .../website/home/paid-stats/lines-bg.svg | 62 ++ .../assets/website/home/paid-stats/solana.svg | 14 + static/locales/en/01-translate/03-footer.json | 12 +- .../en/01-translate/04-01-pages-home.json | 206 ++-- .../05-12-components-home-agent-faq.json | 69 ++ .../06-03-notifications-info-bar.json | 6 +- static/locales/en/translation.json | 309 +++--- 42 files changed, 2681 insertions(+), 1070 deletions(-) create mode 100644 src/components/Home/AgenticScaleSection.tsx create mode 100644 src/components/Home/BuiltToScaleBanner.tsx create mode 100644 src/components/Home/ClosingCTA.tsx create mode 100644 src/components/Home/FeatureCard.tsx create mode 100644 src/components/Home/PaidStatsRow.tsx create mode 100644 src/components/Home/ProblemNarrative.tsx create mode 100644 src/components/Home/SolutionPanel.tsx create mode 100644 src/components/Home/UniversalStatCallout.tsx create mode 100644 src/config/AgentFeaturesListA.ts create mode 100644 src/config/AgentFeaturesListB.ts create mode 100644 src/config/HomeAgentFAQsList.tsx create mode 100644 src/config/PaidStatsList.ts create mode 100644 src/config/ProblemNarrativeList.ts create mode 100644 static/assets/website/home/built-to-scale/push-logo-mark.svg create mode 100644 static/assets/website/home/built-to-scale/ring-pattern.png create mode 100644 static/assets/website/home/feature-grid-b/universal-access.png create mode 100644 static/assets/website/home/feature-grid-b/universal-contracts.png create mode 100644 static/assets/website/home/feature-grid-b/universal-read-write.png create mode 100644 static/assets/website/home/paid-stats/arbitrum.svg create mode 100644 static/assets/website/home/paid-stats/base.svg create mode 100644 static/assets/website/home/paid-stats/bnb-chain.svg create mode 100644 static/assets/website/home/paid-stats/ethereum.svg create mode 100644 static/assets/website/home/paid-stats/glow-ellipse.svg create mode 100644 static/assets/website/home/paid-stats/lines-bg.svg create mode 100644 static/assets/website/home/paid-stats/solana.svg create mode 100644 static/locales/en/01-translate/05-12-components-home-agent-faq.json diff --git a/docusaurus.config.js b/docusaurus.config.js index 8937ab017a6f..e9a984474754 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -212,7 +212,7 @@ const config = { tagName: 'link', attributes: { rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&display=swap" rel="stylesheet', + href: 'https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Fira+Code:wght@300..700&family=IBM+Plex+Mono:wght@400;500;600;700&display=swap" rel="stylesheet', }, }, { diff --git a/src/components/Accordion/index.tsx b/src/components/Accordion/index.tsx index 3500db4aa231..432c9bcfe854 100644 --- a/src/components/Accordion/index.tsx +++ b/src/components/Accordion/index.tsx @@ -27,6 +27,13 @@ interface AccordionProps { firstOpen?: boolean; fontWeight?: string; fontSize?: string; + /** + * 'pushCore' applies the Push Core landing spec (Figma node 49245:16793): + * 32px item padding, a 12px gap before an open answer, a 1.5px #2d2d32 + * rule and a 32px toggle. Omit it and the accordion renders exactly as it + * does on the other four pages that share this component. + */ + variant?: 'default' | 'pushCore'; } const Accordion: React.FC = ({ @@ -34,7 +41,9 @@ const Accordion: React.FC = ({ firstOpen, fontWeight, fontSize, + variant = 'default', }) => { + const isPushCore = variant === 'pushCore'; const [activeIndex, setActiveIndex] = useState( firstOpen === false ? null : 0 ); @@ -49,22 +58,32 @@ const Accordion: React.FC = ({ return (
{items.map((item, index) => ( - - toggleAccordion(index)}> + + toggleAccordion(index)} + $pushCore={isPushCore} + $open={activeIndex === index} + >

{item.title || item.question}

{activeIndex === index ? ( - + ) : ( - + )}
@@ -107,15 +126,21 @@ const Accordion: React.FC = ({ }; const AccordionSection = styled.div` - border-bottom: 1px solid var(--ifm-color-gray-200); + border-bottom: ${(props) => + props.$pushCore ? '1.5px solid #2d2d32' : '1px solid var(--ifm-color-gray-200)'}; h3 { white-space: pre-wrap; } `; +// In the pushCore spec an item is 32px-padded, but an open one tightens to a +// 12px gap before its answer (Figma: header at y=0, answer at y=44). const AccordionParent = styled.div` - padding: 24px 0; + padding: ${(props) => { + if (!props.$pushCore) return '24px 0'; + return props.$open ? '32px 0 12px 0' : '32px 0'; + }}; display: flex; flex-direction: row; flex: 1; diff --git a/src/components/Home/AgenticScaleSection.tsx b/src/components/Home/AgenticScaleSection.tsx new file mode 100644 index 000000000000..f60af795305e --- /dev/null +++ b/src/components/Home/AgenticScaleSection.tsx @@ -0,0 +1,335 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// External Components +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +// Internal Components +import { Section } from '@site/src/css/SharedStyling'; +import FeatureCard from '@site/src/components/Home/FeatureCard'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import { device } from '@site/src/config/globals'; +import { AgentFeaturesListA } from '@site/src/config/AgentFeaturesListA'; + +// Import Assets +import RingPattern from '@site/static/assets/website/home/built-to-scale/ring-pattern.png'; +import PushLogoMark from '@site/static/assets/website/home/built-to-scale/push-logo-mark.svg'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +// Figma's background plate (Rectangle 42282) is 1403px wide on a 1440px frame, +// i.e. inset by (1440 - 1403) / 2 on each side. It sits at Section level rather +// than inside the page's max-width Content container, so the page gutter can't +// constrain it — that's what makes it read as full-bleed. +const PANEL_INSET = 18.5; + +// Vertical anchors, in design pixels measured from the top of that plate. +const VISUAL_HEIGHT = 726; // image 32 — the slot for the incoming animation +const PINK_CARD_TOP = 177; +const BLEND_TOP = 552; // Rectangle 42285 +const BLEND_HEIGHT = 544; +const BODY_TOP = 743; // Frame 37246 +const ROW_ONE_HEIGHT = 534; +const ROW_TWO_HEIGHT = 418; + +export default function AgenticScaleSection() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + + return ( +
+ + {/* Top half: reserved for the animation the design team will supply. + The exported ring artwork stands in for it until then. */} + + + + {/* Blurred wash that melts the artwork above into the card grid below. */} + +
+ ); +} + +// The single gradient plate behind the whole section. Its final stop is the +// page background, so the section dissolves into the page instead of ending on +// a seam. Stops are in design pixels from the top, because everything anchored +// to them (artwork, wash, card rows) is fixed-height too. +const Panel = styled.div` + position: relative; + flex: 1; + margin: 0 ${PANEL_INSET}px; + border-radius: 48px 48px 0 0; + overflow: hidden; + /* Figma's Rectangle 42282 stops, at their design pixel offsets. #fbe9fe is a + single peak at 1207px — not a plateau — so the light stays a narrow band + around the first card row and darkens continuously from there, reaching + #180621 just below the second row (which ends ~1841px). The tail is only + carried far enough to settle on the page colour without banding. */ + background: linear-gradient( + 180deg, + #d548ec 0px, + #d548ec 454px, + #fbe9fe 1207px, + #180621 1894px, + var(--ifm-color-black) 97% + ); + + @media ${device.mobileL} { + margin: 0 12px; + border-radius: 24px 24px 0 0; + /* Same shape as the design, in percentages — the stacked mobile layout's + height varies with copy, so fixed pixel offsets wouldn't line up. */ + background: linear-gradient( + 180deg, + #d548ec 0%, + #d548ec 17%, + #fbe9fe 46%, + #180621 72%, + var(--ifm-color-black) 95% + ); + } +`; + +const TopVisual = styled.div` + position: relative; + width: 100%; + height: ${VISUAL_HEIGHT}px; + + @media ${device.laptop} { + height: 560px; + } + + @media ${device.tablet} { + height: 440px; + } + + @media ${device.mobileL} { + height: 320px; + } +`; + +const RingArtwork = styled.div` + position: absolute; + inset: 0; + background-image: url(${RingPattern}); + background-size: cover; + background-position: center 30%; + border-radius: 48px; + mix-blend-mode: lighten; + pointer-events: none; + + @media ${device.mobileL} { + border-radius: 24px; + } +`; + +const PinkCard = styled.div` + position: absolute; + top: ${PINK_CARD_TOP}px; + left: 50%; + transform: translateX(-50%); + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 44px; + width: 800px; + max-width: calc(100% - 48px); + padding: 40px; + box-sizing: border-box; + background: var(--ifm-color-custom-pink); + + @media ${device.laptop} { + top: 140px; + gap: 32px; + padding: 32px 24px; + max-width: calc(100% - 48px); + } + + @media ${device.tablet} { + top: 110px; + gap: 24px; + } + + @media ${device.mobileL} { + top: 72px; + gap: 16px; + padding: 24px 16px; + max-width: calc(100% - 32px); + } +`; + +const LogoMark = styled.div` + width: 46px; + height: auto; + display: flex; + + svg { + width: 100%; + height: auto; + } + + @media ${device.mobileL} { + width: 32px; + } +`; + +const BannerHeading = styled.h2` + margin: 0; + text-align: center; + color: var(--ifm-color-white); + font-family: ${FONT_MONO}; + font-weight: 500; + font-size: 38px; + letter-spacing: -0.06em; + line-height: 1.2; + + span { + display: block; + white-space: nowrap; + } + + /* The two lines are set nowrap at full size; below this they no longer fit, + so let them wrap and step the size down instead of overflowing. */ + @media ${device.laptop} { + font-size: 28px; + + span { + white-space: normal; + } + } + + @media ${device.tablet} { + font-size: 22px; + } + + @media ${device.mobileL} { + font-size: 17px; + } +`; + +const BlendWash = styled.div` + position: absolute; + top: ${BLEND_TOP}px; + left: 0; + right: 0; + height: ${BLEND_HEIGHT}px; + z-index: 1; + background: linear-gradient(180deg, #de6ef0 0%, #fae7fe 100%); + filter: blur(50px); + pointer-events: none; + + @media ${device.mobileL} { + top: 300px; + height: 260px; + filter: blur(30px); + } +`; + +const Body = styled.div` + position: relative; + z-index: 2; + padding-top: ${BODY_TOP - VISUAL_HEIGHT}px; + + @media ${device.laptop} { + padding-top: 40px; + } + /* Tail for the gradient to settle onto the page colour. Sized so the gap + from the last card down to the Grid B title lands on Figma's 322px + (node 49233:16551's block ends at 1074, Frame 37236 starts at 1396). */ + padding-bottom: 197px; + + @media ${device.mobileL} { + padding-top: 48px; + padding-bottom: 80px; + } +`; + +const BodyInner = styled.div` + width: 100%; + max-width: 1292px; + margin: 0 auto; + padding: 0 16px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 24px; +`; + +const GridTitle = styled.h2` + margin: 0 0 40px 0; + text-align: center; + color: var(--ifm-color-white); + font-family: ${FONT_MONO}; + font-weight: 500; + font-size: 3rem; + letter-spacing: -0.06em; + line-height: 1.2; + + @media ${device.mobileL} { + margin: 0 0 8px 0; + font-size: 1.75rem; + } +`; + +const CardRow = styled.div` + display: flex; + align-items: stretch; + gap: 24px; + + > * { + ${(props) => (props.$height ? `height: ${props.$height}px;` : '')} + } + + /* Three fixed-height cards need real width; below the laptop breakpoint + they'd squeeze to ~190px, so stack them and let height follow content. */ + @media ${device.laptop} { + flex-direction: column; + + > * { + height: auto; + min-height: 260px; + } + } +`; diff --git a/src/components/Home/BuiltToScaleBanner.tsx b/src/components/Home/BuiltToScaleBanner.tsx new file mode 100644 index 000000000000..bcb1da3db417 --- /dev/null +++ b/src/components/Home/BuiltToScaleBanner.tsx @@ -0,0 +1,118 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// External Components +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +// Internal Components +import { Content, Section } from '@site/src/css/SharedStyling'; + +// Internal Configs +import { device } from '@site/src/config/globals'; + +// Import Assets +import RingPattern from '@site/static/assets/website/home/built-to-scale/ring-pattern.png'; +import PushLogoMark from '@site/static/assets/website/home/built-to-scale/push-logo-mark.svg'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +export default function BuiltToScaleBanner() { + const { t } = useTranslation(); + + return ( +
+ + + + +
+ ); +} + +const BannerWrapper = styled.div` + position: relative; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border-radius: 48px; + padding: 44px 0; + background: var(--ifm-color-custom-pink); +`; + +const RingBackground = styled.div` + position: absolute; + inset: 0; + background-image: url(${RingPattern}); + background-size: cover; + background-position: center 30%; + mix-blend-mode: lighten; + pointer-events: none; +`; + +const PinkCard = styled.div` + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 44px; + padding: 40px; + max-width: 800px; + width: 100%; + background: var(--ifm-color-custom-pink); + border-radius: 24px; + + @media ${device.mobileL} { + max-width: 100%; + margin: 0 16px; + } +`; + +const LogoMark = styled.div` + width: 46px; + height: auto; + display: flex; + + svg { + width: 100%; + height: auto; + } +`; + +const Heading = styled.h2` + margin: 0; + text-align: center; + color: var(--ifm-color-white); + font-family: ${FONT_MONO}; + font-weight: 500; + font-size: 38px; + letter-spacing: -0.06em; + line-height: 1.2; + + span { + display: block; + } + + @media ${device.mobileL} { + font-size: 22px; + } +`; diff --git a/src/components/Home/ClosingCTA.tsx b/src/components/Home/ClosingCTA.tsx new file mode 100644 index 000000000000..f1a0a3eaa772 --- /dev/null +++ b/src/components/Home/ClosingCTA.tsx @@ -0,0 +1,132 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// React + Web3 Essentials +import useBaseUrl from '@docusaurus/useBaseUrl'; + +// External Components +import { useTranslation } from 'react-i18next'; +import { BsArrowRight } from 'react-icons/bs'; +import styled from 'styled-components'; + +// Internal Components +import { A, Content, H2, ItemV, Section } from '@site/src/css/SharedStyling'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import { device } from '@site/src/config/globals'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +export default function ClosingCTA() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + + return ( +
+ + {/* Reserved for the dot-matrix hand animation. Held at the design's + footprint so dropping the real asset in needs no layout change. */} + + + +

+ {t('pages.home.closing-cta.title-line1')} +
+ {t('pages.home.closing-cta.title-line2')} +

+ + {t('pages.home.closing-cta.subtitle')} + + + {t('pages.home.closing-cta.cta')} + +
+
+
+ ); +} + +const AnimationSlot = styled.div` + width: 100%; + height: 420px; + + @media ${device.laptop} { + height: 320px; + } + + @media ${device.tablet} { + height: 240px; + } + + @media ${device.mobileL} { + height: 160px; + } +`; + +const CTABody = styled(ItemV)` + align-items: center; + justify-content: center; + gap: 16px; + margin-top: 48px; + + @media ${device.mobileL} { + margin-top: 24px; + } +`; + +const Subtitle = styled.p` + margin: 0; + text-align: center; + font-family: 'DM Sans', sans-serif; + font-size: 1.125rem; + font-weight: 400; + line-height: 1.5; + letter-spacing: -0.36px; + color: var(--ifm-color-white); + + @media ${device.mobileL} { + font-size: 1rem; + } +`; + +const CTAButton = styled(A)` + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + height: 58px; + padding: 16px 32px; + margin-top: 24px; + box-sizing: border-box; + background: var(--ifm-color-custom-pink); + border: 1px solid rgba(255, 255, 255, 0.3); + border-radius: 16px; + color: var(--ifm-color-white); + font-family: 'DM Sans', sans-serif; + font-size: 1.125rem; + font-weight: 600; + line-height: 1rem; + letter-spacing: -0.03em; +`; diff --git a/src/components/Home/FeatureCard.tsx b/src/components/Home/FeatureCard.tsx new file mode 100644 index 000000000000..21d4c5490db2 --- /dev/null +++ b/src/components/Home/FeatureCard.tsx @@ -0,0 +1,135 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// External Components +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +export default function FeatureCard({ item }) { + const { t } = useTranslation(); + + if (item.cardStyle === 'divided') { + return ( + + {item.icon && ( + + ); + } + + return ( + + + {t(item.titleKey)} + {t(item.descriptionKey)} + + + ); +} + +const BorderedCard = styled.div` + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + /* Figma's stroke is a vertical linear gradient — #ffffff at the top to + #eb8afb at the bottom — drawn at 25% opacity, Outside, 1px. Extracted from + the node's alpha channel (alpha 63/255 = 24.7%). + CSS can't put a gradient on a border while honouring border-radius, so the + border area is painted by a border-box gradient layer and the fill by a + padding-box layer on top. Keeping the stops translucent (rather than + pre-flattening them) reproduces Figma's Outside placement, where the + stroke blends with the section gradient behind the card. */ + background: + linear-gradient(#090909, #090909) padding-box, + linear-gradient( + 180deg, + rgba(255, 255, 255, 0.25) 0%, + rgba(235, 138, 251, 0.25) 100% + ) + border-box; + border: 1px solid transparent; + border-radius: 24px; + padding: 32px; + box-sizing: border-box; +`; + +const BorderedTitle = styled.p` + margin: 0; + /* Titles carry their own line breaks, matching the design's wrap points. */ + white-space: pre-line; + font-family: ${FONT_MONO}; + font-weight: 400; + font-size: 2rem; + letter-spacing: -0.06em; + line-height: 1.2; + color: var(--ifm-color-white); +`; + +const BorderedDescription = styled.p` + /* Figma spaces the title and body by 24px (spacing-md), not 16px. */ + margin: 24px 0 0 0; + font-size: 1rem; + letter-spacing: -0.02em; + line-height: 1.5; + color: var(--ifm-color-white); +`; + +const DividedCard = styled.div` + flex: 1; + display: flex; + flex-direction: column; + gap: 8px; + padding: 32px; + border-left: 1px solid #2c2c35; + + &:first-child { + border-left: none; + } +`; + +const IconImage = styled.img` + width: 120px; + height: 120px; +`; + +const CardBody = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + +const DividedTitle = styled.p` + margin: 0; + white-space: pre-line; + font-family: ${FONT_MONO}; + font-weight: 500; + font-size: 2rem; + letter-spacing: -0.06em; + line-height: 1.2; + color: var(--ifm-color-white); +`; + +const DividedDescription = styled.p` + margin: 0; + font-size: 1.125rem; + letter-spacing: -0.02em; + line-height: 1.5; + color: #a9a6b2; +`; diff --git a/src/components/Home/PaidStatsRow.tsx b/src/components/Home/PaidStatsRow.tsx new file mode 100644 index 000000000000..71c931e3cf0b --- /dev/null +++ b/src/components/Home/PaidStatsRow.tsx @@ -0,0 +1,375 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// External Components +import Lottie from 'lottie-react'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +// Internal Components +import { Content, H2, ItemH, ItemV, Section, Span } from '@site/src/css/SharedStyling'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import { device } from '@site/src/config/globals'; +import { PaidStatsList } from '@site/src/config/PaidStatsList'; + +// Import Assets +import GlowEllipse from '@site/static/assets/website/home/paid-stats/glow-ellipse.svg'; +import instantInteroperability from '@site/static/assets/website/interoperability/instant-interoperability.json'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +// Chain marks are exported from Figma at 43.119px and overlap by 11.179px +// (node 49245:16690). Icons the design team hasn't supplied yet are skipped +// rather than crashing the build, so dropping the file in is all that's needed. +const chainIcon = (name) => { + try { + return require( + `@site/static/assets/website/home/paid-stats/${name}.svg` + ).default; + } catch (err) { + return null; + } +}; + +export default function PaidStatsRow() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + + return ( +
+ + + + + + {t(PaidStatsList.meta.eyebrow)} + + + + + + {t(PaidStatsList.meta.title)}{' '} + {t(PaidStatsList.meta.titleGradient)} + + + {t(PaidStatsList.meta.paragraph)} + + + + + {/* The lines belong to the stats row, not the section — Figma anchors + Layer_1's box 227px above Frame 37131, so the strands sweep up + behind the heading and settle across the stats. */} + + + + {PaidStatsList.stats.map((stat) => ( + + {t(stat.titleKey)} + + {stat.type === 'text' && ( + {t(stat.valueKey)} + )} + + {stat.type === 'chains' && ( + + + {stat.chains + .map((chain) => ({ ...chain, src: chainIcon(chain.icon) })) + .filter((chain) => chain.src) + .map((chain) => ( + + ))} + + {t(stat.badgeTextKey)} + + )} + + ))} + + + +
+ ); +} + +// Figma's Ellipse 42 is a 587px circle whose blur spills 51.11% past its box, +// so the exported artwork is 1187px square. Centred on the section on both +// axes — the design places its centre on the content block's centre. +const GlowBackground = styled.div` + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 1187px; + height: 1187px; + pointer-events: none; + z-index: 0; + + @media ${device.laptop} { + width: 820px; + height: 820px; + } + + @media ${device.mobileL} { + width: 520px; + height: 520px; + } + + svg { + width: 100%; + height: 100%; + display: block; + } +`; + +// The flowing lines behind this section are the same Lottie the old +// interoperability section used, rather than a flat SVG. +// Positioning context for the lines so they track the stats row rather than +// the section as a whole. +const StatsStage = styled.div` + position: relative; + width: 100%; + margin-top: 100px; + + @media ${device.mobileL} { + margin-top: 48px; + } +`; + +// Anchored to the stats row so the strands keep crossing the stats however the +// copy above reflows. The offset is set from Layer_1's central axis (the +// zero-height vector at y=262.7 in its box), which Figma places ~33% down +// Frame 37131 — the Lottie's own artwork sits slightly higher inside its box +// than Figma's vectors do, so this is 40px lower than a box-to-box match. +const LinesAnimation = styled.div` + position: absolute; + left: 50%; + top: -167px; + transform: translateX(-50%); + width: 1317px; + height: 463px; + max-width: none; + /* Faint in the design — the lines read as texture behind the content. */ + opacity: 0.2; + pointer-events: none; + z-index: 0; + + svg { + width: 100%; + height: 100%; + display: block; + } + + /* The strands are 1317px wide — narrower than that they're mostly cropped + and just add noise behind the stats. */ + @media ${device.laptop} { + display: none; + } +`; + +const Eyebrow = styled(ItemH)` + justify-content: flex-start; + margin-bottom: 16px; + position: relative; + z-index: 1; +`; + +const PaidHeaderRow = styled(ItemV)` + flex-direction: row; + align-items: center; + /* Figma pins the paragraph to the right edge of the 1200px content block + (node 49245:16672 sits at x=806 with w=394). Our container is wider, so + space-between keeps it flush right instead of leaving slack there. */ + justify-content: space-between; + width: 100%; + position: relative; + z-index: 1; + gap: 236px; + + @media ${device.laptop} { + flex-direction: column; + align-items: flex-start; + gap: 24px; + } +`; + +const TitleBlock = styled(H2)` + margin: 0 !important; + flex: 0 0 auto; + max-width: 570px; + + @media ${device.laptop} { + font-size: 2.25rem !important; + max-width: 100%; + } + + @media ${device.tablet} { + font-size: 2rem !important; + } +`; + +const HeaderParagraph = styled.p` + margin: 0; + flex: 0 0 auto; + max-width: 394px; + font-family: 'DM Sans', sans-serif; + font-size: 1.25rem; + font-weight: 400; + line-height: 1.5; + letter-spacing: -0.4px; + color: var(--ifm-color-white); + /* Two fixed lines, per the design's split of this copy. */ + white-space: pre-line; + + @media ${device.mobileL} { + max-width: 100%; + } +`; + +const GradientWord = styled.span` + color: var(--ifm-color-custom-pink); +`; + +// No rule above this row in the design — the stats sit directly on the +// background, 100px below the header block (Frame 37131 at y=234, header 134). +const StatRow = styled(ItemH)` + align-items: flex-start; + justify-content: center; + gap: 151px; + position: relative; + z-index: 1; + + /* Four stats across needs the full width; below that go two-up, then + single column on phones. */ + @media ${device.laptop} { + flex-wrap: wrap; + gap: 48px 32px; + } + + @media ${device.mobileL} { + flex-direction: column; + gap: 32px; + } +`; + +const StatItem = styled(ItemV)` + gap: 24px; + align-items: center; + justify-content: center; + text-align: center; + flex: ${(props) => (props.$fill ? '1 0 0' : '0 0 auto')}; + min-width: 0; + + /* Two-up, then single column. Declared here rather than as a child selector + on the row, which this component's own flex above would override. */ + @media ${device.laptop} { + flex: 1 1 40%; + min-width: 220px; + } + + @media ${device.mobileL} { + flex: 1 1 100%; + width: 100%; + } +`; + +const StatTitle = styled.span` + font-family: 'DM Sans', sans-serif; + font-size: 1rem; + font-weight: 600; + letter-spacing: 0.8px; + line-height: 1.2; + text-transform: uppercase; + text-align: center; + white-space: nowrap; + color: var(--ifm-color-white); + + @media ${device.laptop} { + white-space: normal; + } +`; + +const StatValue = styled.span` + font-family: 'DM Sans', sans-serif; + font-size: 3.25rem; + font-weight: 400; + letter-spacing: -1.04px; + line-height: 1.2; + text-align: center; + white-space: nowrap; + color: var(--ifm-color-white); + + @media ${device.laptop} { + white-space: normal; + } +`; + +const ChainRow = styled(ItemV)` + align-items: center; + justify-content: center; + gap: 8px; +`; + +const ChainIconStack = styled(ItemH)` + justify-content: center; + align-items: center; + flex-wrap: nowrap; +`; + +const ChainIcon = styled.img` + width: 43.119px; + height: 43.119px; + border-radius: 50%; + flex: 0 0 auto; + + &:not(:last-child) { + margin-right: -11.179px; + } +`; + +const MoreChainsBadge = styled.span` + display: inline-flex; + align-items: center; + justify-content: center; + height: 21px; + padding: 0 8px; + box-sizing: border-box; + border-radius: 12px; + border: 1px solid var(--ifm-color-pink-100); + font-family: 'DM Sans', sans-serif; + font-size: 0.5625rem; + font-weight: 700; + letter-spacing: 0.45px; + color: var(--ifm-color-pink-100); + white-space: nowrap; +`; diff --git a/src/components/Home/ProblemNarrative.tsx b/src/components/Home/ProblemNarrative.tsx new file mode 100644 index 000000000000..a2205978862c --- /dev/null +++ b/src/components/Home/ProblemNarrative.tsx @@ -0,0 +1,359 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// React + Web3 Essentials +import { useEffect, useRef } from 'react'; + +// External Components +import { gsap } from 'gsap'; +import { ScrollTrigger } from 'gsap/ScrollTrigger'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +// Internal Components +import { Content, H2, ItemH, ItemV, Section, Span } from '@site/src/css/SharedStyling'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import GLOBALS, { device } from '@site/src/config/globals'; +import { ProblemNarrativeList } from '@site/src/config/ProblemNarrativeList'; + +gsap.registerPlugin(ScrollTrigger); + +// Hard floor for where a stuck card parks, so it can never tuck under the +// fixed header on short viewports. +const HEADER_OFFSET = GLOBALS.HEADER.HEIGHT + GLOBALS.HEADER.OUTER_MARGIN.DESKTOP.TOP + 24; + +// Breathing room the parked card always keeps below the header, on top of the +// header floor. Centring alone is not enough: on a short laptop viewport +// (~740px) the centred position lands only a few pixels below the header, so +// the stack still reads as glued to the nav. +const MIN_PARK_GAP = 64; + +// A parked card sits vertically centred in the viewport rather than pinned to +// the top, which reads far better when the card is shorter than the screen — +// but never closer to the nav than the gap above, and never so low that the +// card's bottom edge runs off the screen on a short window. +const parkOffset = (cardHeight) => { + if (typeof window === 'undefined') return HEADER_OFFSET; + + const viewport = window.innerHeight; + const centred = Math.round((viewport - cardHeight) / 2); + const preferred = Math.max(HEADER_OFFSET + MIN_PARK_GAP, centred); + const ceiling = Math.max(HEADER_OFFSET, viewport - cardHeight - 24); + + return Math.min(preferred, ceiling); +}; + +// Vertical gap between the cards while they are still stacked in flow. +const CARD_GAP = 48; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +// Exact 5x5 dot-grid glyph from Figma (node 49322:1879 and its repeats) — red accent +// cells (rounded, 5.739px) and gray filler cells (3.826px) at fixed grid coordinates. +const RED_CELLS = [ + [0, 0], [0, 38.26], [9.57, 9.57], [9.57, 28.7], [19.13, 19.13], + [28.7, 9.57], [28.7, 28.7], [38.26, 0], [38.26, 38.26], +]; +const GRAY_CELLS = [ + [0, 9.57], [0, 19.13], [0, 28.7], + [9.57, 0], [9.57, 19.13], [9.57, 38.26], + [19.13, 0], [19.13, 9.57], [19.13, 28.7], [19.13, 38.26], + [28.7, 0], [28.7, 19.13], [28.7, 38.26], + [38.26, 9.57], [38.26, 19.13], [38.26, 28.7], +]; + +const DotGridIcon = () => ( + +); + +export default function ProblemNarrative() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + // Stack only where there's room for it; below this the cards read better + // as a simple vertical list. + const isCompact = useMediaQuery(device.laptop); + + const stageRef = useRef(null); + const cardRefs = useRef([]); + const parkRef = useRef(HEADER_OFFSET); + const setCardRef = (index) => (el) => { + cardRefs.current[index] = el; + }; + + // The stack is pure CSS `position: sticky` in natural document flow: every + // card keeps its own place in the flow, so the section's height is always + // the true sum of its cards and the following section can never ride up + // underneath it. The cards differ by a line or two of wrapped copy though, + // so equalise their heights — otherwise the incoming card is shorter than + // the one it is meant to cover and leaves a sliver showing. + useEffect(() => { + const cards = cardRefs.current.filter(Boolean); + if (cards.length < 2) return undefined; + + const equalize = () => { + cards.forEach((card) => { + card.style.minHeight = ''; + }); + + if (isCompact) { + ScrollTrigger.refresh(); + return; + } + + const tallest = Math.max(...cards.map((card) => card.offsetHeight)); + cards.forEach((card) => { + card.style.minHeight = `${tallest}px`; + }); + + // Park the stack vertically centred for this viewport. Published as a + // custom property so the sticky CSS and the GSAP trigger below always + // read the exact same number. + parkRef.current = parkOffset(tallest); + stageRef.current?.style.setProperty('--stack-top', `${parkRef.current}px`); + + // Heights and offsets just changed, so every trigger needs recomputing. + ScrollTrigger.refresh(); + }; + + equalize(); + + // Re-measure once webfonts land — IBM Plex Mono changes how the story + // copy wraps, and therefore how tall each card is. + if (document.fonts && document.fonts.ready) { + document.fonts.ready.then(equalize); + } + + window.addEventListener('resize', equalize); + return () => { + window.removeEventListener('resize', equalize); + cards.forEach((card) => { + if (card) card.style.minHeight = ''; + }); + }; + }, [isCompact]); + + // Cosmetic only: as the incoming card climbs over the one beneath it, the + // lower card recedes so the stack reads as depth rather than a flat swap. + // This touches transform/opacity exclusively — never layout — so it cannot + // disturb the sticky flow above. + useEffect(() => { + if (isCompact) return undefined; + + const cards = cardRefs.current.filter(Boolean); + if (cards.length < 2 || !stageRef.current) return undefined; + + const ctx = gsap.context(() => { + cards.slice(0, -1).forEach((card) => { + gsap.to(card, { + scale: 0.93, + opacity: 0.45, + ease: 'none', + scrollTrigger: { + trigger: stageRef.current, + // From the moment the first card parks… + start: () => `top top+=${parkRef.current}`, + // …until the stack releases, i.e. the card above is fully covered. + end: () => `bottom top+=${parkRef.current + card.offsetHeight}`, + scrub: true, + invalidateOnRefresh: true, + }, + }); + }); + }, stageRef); + + return () => ctx.revert(); + }, [isCompact]); + + return ( +
+ + +

+ {t(ProblemNarrativeList.meta.titleLine1)} +
+ + {t(ProblemNarrativeList.meta.titleLine2)}{' '} + {t(ProblemNarrativeList.meta.titleGradient)} + +

+ + {t(ProblemNarrativeList.meta.subtitle)} + +
+ + + {ProblemNarrativeList.vignettes.map((vignette, index) => ( + + + + + {t(vignette.story)} + + + + + {vignette.consequences.map((consequence, i) => ( + + + + {t(consequence.text)} + + + ))} + + + + ))} + +
+
+ ); +} + +const NarrativeHeader = styled(ItemV)` + align-items: center; + max-width: 780px; + margin: 0 auto; +`; + +const GradientWord = styled.span` + color: var(--ifm-color-custom-pink); +`; + +// Plain block in normal flow. Deliberately no overflow/transform/filter here: +// any of those would turn this into the sticky containing block and the stack +// would silently stop working. +const Stage = styled.div` + position: relative; + width: 100%; + margin-top: 64px; + + @media ${device.mobileL} { + margin-top: 40px; + } +`; + +// One slot per card, stacked normally. Each sticks at the same offset, so a +// later card climbs over an earlier one and — being later in the DOM — paints +// on top of it without needing any z-index juggling. +const CardSlot = styled.div` + position: sticky; + top: var(--stack-top, ${HEADER_OFFSET}px); + width: 100%; + + &:not(:last-child) { + margin-bottom: ${CARD_GAP}px; + } + + @media ${device.laptop} { + position: static; + top: auto; + + &:not(:last-child) { + margin-bottom: 24px; + } + } +`; + +const VignetteCard = styled(ItemV)` + align-items: ${(props) => (props.align === 'right' ? 'flex-end' : 'flex-start')}; + justify-content: space-between; + gap: 157px; + width: 100%; + padding: 48px; + border-radius: 48px; + background: ${(props) => props.bg}; + box-sizing: border-box; + will-change: transform; + + @media ${device.laptop} { + align-items: flex-start; + justify-content: flex-start; + gap: 48px; + padding: 40px 32px; + min-height: 0 !important; + } + + @media ${device.mobileL} { + gap: 32px; + padding: 28px 20px; + } +`; + +// 465px is the Figma width, but it has to be a cap rather than a fixed width: +// as a fixed width it becomes a min-content floor that inflates the card (and +// then the whole Content column) past the viewport on narrow screens. +const StoryText = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-width: 465px; + min-width: 0; +`; + +const ConsequenceRow = styled(ItemH)` + gap: 64px; + align-items: flex-start; + width: 100%; + + @media ${device.laptop} { + flex-direction: column; + gap: 24px; + } +`; + +const ConsequenceItem = styled(ItemH)` + flex: 1; + align-items: flex-start; + justify-content: flex-start; + flex-wrap: nowrap; + gap: 24px; + + svg { + flex: 0 0 auto; + } +`; diff --git a/src/components/Home/RecentBlogPosts.js b/src/components/Home/RecentBlogPosts.js index 7c7921dee50b..8ad6438d92b3 100644 --- a/src/components/Home/RecentBlogPosts.js +++ b/src/components/Home/RecentBlogPosts.js @@ -18,7 +18,7 @@ import { Spacer, } from '@site/src/components/reusables/date.tsx'; -const RecentBlogPosts = () => { +const RecentBlogPosts = ({ count = 3 }) => { const { recentBlogs } = useFetchRecentBlogs(); const { t } = useTranslation(); @@ -48,7 +48,7 @@ const RecentBlogPosts = () => { role='list' aria-label={t('pages.home.blog-section.blog-posts-list-aria-label')} > - {recentBlogs?.slice(0, 3).map((postItem, index) => { + {recentBlogs?.slice(0, count).map((postItem, index) => { const blogPostAriaLabel = t( 'pages.home.blog-section.blog-post-aria-label', { diff --git a/src/components/Home/SolutionPanel.tsx b/src/components/Home/SolutionPanel.tsx new file mode 100644 index 000000000000..017f7088eb0e --- /dev/null +++ b/src/components/Home/SolutionPanel.tsx @@ -0,0 +1,117 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// External Components +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +// Internal Components +import { Content, H2, ItemH, ItemV, Section, Span } from '@site/src/css/SharedStyling'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import { device } from '@site/src/config/globals'; + +const FONT_MONO = "'IBM Plex Mono', monospace"; + +export default function SolutionPanel() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + + return ( + + + +

+ {/* Three fixed lines, per Figma. The design's 530px text box wraps + naturally into "Making AI / Universally / accountable"; forcing + the breaks keeps that shape at any container width instead of + depending on where the copy happens to wrap. */} + {t('pages.home.solution-panel.title-prefix')} +
+ {t('pages.home.solution-panel.title-highlight')} +
+ {t('pages.home.solution-panel.title-suffix')} +

+ + {t('pages.home.solution-panel.paragraph')} + +
+ +
+
+ ); +} + +// Figma puts 385px between the bottom of the last problem-narrative card and +// the top of this title (node 49233:16550 at y=2724 vs the card block ending +// at y=2339). The two sections' own paddings already supply 253px of that, so +// this makes up the remaining lead-in. +const SolutionSection = styled(Section)` + margin-top: 132px; + + @media ${device.mobileL} { + margin-top: 40px; + } +`; + +const TextRow = styled(ItemH)` + align-items: center; + gap: 139px; + margin-bottom: 64px; + + h2, + span { + flex: 1; + } + + @media ${device.laptop} { + flex-direction: column; + align-items: flex-start; + gap: 24px; + margin-bottom: 40px; + } + + @media ${device.mobileL} { + margin-bottom: 32px; + } +`; + +const GradientWord = styled.span` + color: var(--ifm-color-custom-pink); +`; + +const SolutionPlaceholder = styled.div` + width: 100%; + height: 635px; + border-radius: 48px; + background: #323232; + + @media ${device.laptop} { + height: 420px; + border-radius: 32px; + } + + @media ${device.mobileL} { + height: 240px; + border-radius: 24px; + } +`; diff --git a/src/components/Home/UniversalStatCallout.tsx b/src/components/Home/UniversalStatCallout.tsx new file mode 100644 index 000000000000..88fe82d7eae9 --- /dev/null +++ b/src/components/Home/UniversalStatCallout.tsx @@ -0,0 +1,142 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +/* eslint-disable */ + +// React + Web3 Essentials +import useBaseUrl from '@docusaurus/useBaseUrl'; + +// External Components +import { useTranslation } from 'react-i18next'; +import { BsArrowRight } from 'react-icons/bs'; +import styled from 'styled-components'; + +// Internal Components +import { A, ItemH } from '@site/src/css/SharedStyling'; +import useMediaQuery from '@site/src/hooks/useMediaQuery'; + +// Internal Configs +import { device } from '@site/src/config/globals'; + +export default function UniversalStatCallout() { + const { t } = useTranslation(); + const isMobile = useMediaQuery(device.mobileL); + + return ( + + + + {t('pages.home.feature-grid-b.stat-callout.value')} + {t('pages.home.feature-grid-b.stat-callout.value-label')} + + + {t('pages.home.feature-grid-b.stat-callout.text')} + + + + + {t('pages.home.feature-grid-b.stat-callout.cta')} + + + + ); +} + +const CalloutRow = styled(ItemH)` + align-items: center; + justify-content: space-between; + gap: 24px; + margin-top: 24px; + + a { + display: flex; + justify-content: center; + align-items: center; + gap: 12px; + /* Figma gives this button an explicit 58px height. */ + min-height: 58px; + box-sizing: border-box; + } + + @media ${device.laptop} { + flex-direction: column; + align-items: flex-start; + gap: 32px; + } + + @media ${device.mobileL} { + a { + width: 100%; + } + } +`; + +const CalloutLeft = styled(ItemH)` + align-items: center; + justify-content: flex-start; + flex-wrap: wrap; + gap: 24px; +`; + +const GradientText = styled.span` + background: linear-gradient(90deg, #f5f1ff 3.116%, #aa48ec 109.09%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-family: 'DM Sans', sans-serif; + font-weight: 600; +`; + +const StatBlock = styled.div` + display: flex; + align-items: flex-end; +`; + +const StatValue = styled(GradientText)` + font-size: 4.8125rem; + letter-spacing: -1.54px; + line-height: 1; +`; + +const StatLabel = styled(GradientText)` + font-size: 1.25rem; + letter-spacing: -0.4px; + /* Figma sets 20px leading on the 20px label — the two lines sit tight. */ + line-height: 20px; + white-space: pre-line; + margin-left: 4px; +`; + +const CalloutText = styled.p` + margin: 0; + font-family: 'DM Sans', sans-serif; + font-weight: 500; + font-size: 1.75rem; + letter-spacing: -0.56px; + line-height: 1.4; + color: var(--ifm-color-white); + /* Two fixed lines, per Figma's split of this sentence. */ + white-space: pre-line; + + @media ${device.tablet} { + font-size: 1.375rem; + } + + @media ${device.mobileL} { + font-size: 1.125rem; + /* Let the sentence reflow rather than forcing the desktop break. */ + white-space: normal; + } +`; diff --git a/src/components/InfoBar.tsx b/src/components/InfoBar.tsx index 750837d7d747..234609834fe0 100644 --- a/src/components/InfoBar.tsx +++ b/src/components/InfoBar.tsx @@ -81,16 +81,18 @@ const BarContainer = styled.div` justify-content: center; gap: 8px; width: 100%; - padding: 0 16px; + padding: 16px; box-sizing: border-box; - height: 58px; + height: 56px; cursor: pointer; background: linear-gradient( 90deg, - #3524ed 0%, + #f898f0 0%, + #e670ee 25%, var(--ifm-color-custom-pink) 50%, - #3524ed 100% + #e670ee 75%, + #f898f0 100% ); /* backdrop-filter: blur(calc(var(--blur-md, 24px) / 2)); */ @@ -203,7 +205,11 @@ const DismissBtn = styled.button` font-size: 18px; cursor: pointer; padding: 0; - height: 48px; + height: 20px; + width: 20px; + display: flex; + align-items: center; + justify-content: center; `; export default InfoBar; diff --git a/src/components/QnA/QnA.tsx b/src/components/QnA/QnA.tsx index 248d7e142855..34b5c4255fee 100644 --- a/src/components/QnA/QnA.tsx +++ b/src/components/QnA/QnA.tsx @@ -30,6 +30,14 @@ interface QnAProps { exploreMoreTextKey?: string; discordUrl?: string; exploreMoreUrl?: string; + titleFontFamily?: string; + titleFontWeight?: string; + /** + * 'pushCore' applies the Push Core landing spec (Figma node 49245:16774): + * 383/793 columns 24px apart, a 321px-wide title, and the larger Discord + * button. Omit it and this renders as it does on the other four pages. + */ + variant?: 'default' | 'pushCore'; faqsList?: Array<{ question: string; section: string; @@ -54,6 +62,9 @@ const QnA: FC = ({ exploreMoreTextKey = 'components.short-faq-snippet.explore-more-text', discordUrl = 'https://discord.com/invite/pushchain', exploreMoreUrl = '/knowledge/faq', + titleFontFamily = 'DM Sans, sans-serif', + titleFontWeight = '600', + variant = 'default', faqsList, getQnAsFunction = getShortFAQsList, }) => { @@ -63,28 +74,32 @@ const QnA: FC = ({ const isMobile = useMediaQuery(device.mobileL); const isLaptop = useMediaQuery(device.laptop); const finalFaqsList = faqsList || getQnAsFunction(); + const isPushCore = variant === 'pushCore'; return (

= ({

-