diff --git a/src/app/(routes)/_components/stats-section.tsx b/src/app/(routes)/_components/stats-section.tsx index 96a23ae..475c155 100644 --- a/src/app/(routes)/_components/stats-section.tsx +++ b/src/app/(routes)/_components/stats-section.tsx @@ -1,27 +1,7 @@ -import { Text } from "../../_components/text"; -import { StatBox } from "../../_components/stat-box"; +import { Text } from "@/app/_components/text"; +import { StatsRow } from "@/app/_components/stats-row"; -import { getProtocolStats } from "../../_lib/scraper"; -import { humanReadableNumber } from "../../_lib/format"; - -async function getFormattedStatsData() { - const protocolStats = await getProtocolStats({ - revalidate: 24 * 60 * 60, // Update once a day - }); - - return { - totalVolumeUsd: `$${humanReadableNumber(protocolStats.totalVolumeUsd)}`, - totalDeposits: `${humanReadableNumber(protocolStats.totalDeposits)}`, - avgFillTimeInMinutes: `${protocolStats.avgFillTimeInMinutes < 1 ? "<" : ""} ${Math.max( - protocolStats.avgFillTimeInMinutes, - 1, - )}m`, - bridgeFee: "<$1", - }; -} - -export async function StatsSection() { - const formattedStatsData = await getFormattedStatsData(); +export function StatsSection() { return (
@@ -38,36 +18,7 @@ export async function StatsSection() { tradeoffs.
-
- - - - -
+
); } diff --git a/src/app/(routes)/across-plus/_components/features-section.tsx b/src/app/(routes)/across-plus/_components/features-section.tsx index 8f8df0e..8284644 100644 --- a/src/app/(routes)/across-plus/_components/features-section.tsx +++ b/src/app/(routes)/across-plus/_components/features-section.tsx @@ -27,7 +27,7 @@ const features = [ export function FeaturesSection() { return ( -
+
{feature.title} - {feature.body} + {feature.body}
))}
diff --git a/src/app/(routes)/across-plus/_components/get-started-section.tsx b/src/app/(routes)/across-plus/_components/get-started-section.tsx index cc7f67a..b58d637 100644 --- a/src/app/(routes)/across-plus/_components/get-started-section.tsx +++ b/src/app/(routes)/across-plus/_components/get-started-section.tsx @@ -1,38 +1,13 @@ import Image from "next/image"; -import { ArrowRightIcon } from "@/app/_components/icons"; -import { Text } from "@/app/_components/text"; +import { GetStartedSection } from "@/app/_components/get-started-section"; import plusGetStartedImage from "@/app/_assets/plus-get-started.png"; -import { INFORMATION_LINKS } from "@/app/_constants"; -export function GetStartedSection() { +export function PlusGetStartedSection() { return ( -
- get started image - - Ready to get started? - -
- - - go to docs - - - {/* TODO: Use correct link */} - - - get it touch - -
- -
-
-
-
+ } + /> ); } diff --git a/src/app/(routes)/across-plus/page.tsx b/src/app/(routes)/across-plus/page.tsx index a7e885b..3e2e7c8 100644 --- a/src/app/(routes)/across-plus/page.tsx +++ b/src/app/(routes)/across-plus/page.tsx @@ -3,8 +3,8 @@ import { Metadata } from "next"; import { HeroSection } from "./_components/hero-section"; import { FeaturesSection } from "./_components/features-section"; import { UseCasesSection } from "./_components/use-cases-section"; -import { SupportedChainsSection } from "./_components/supported-chains-section"; -import { GetStartedSection } from "./_components/get-started-section"; +import { SupportedChainsSection } from "@/app/_components/supported-chains-section"; +import { PlusGetStartedSection } from "./_components/get-started-section"; export const metadata: Metadata = { title: "Across Plus", @@ -17,8 +17,8 @@ export default function PlusLanding() { - - + + ); } diff --git a/src/app/(routes)/across-settlement/_components/features-section.tsx b/src/app/(routes)/across-settlement/_components/features-section.tsx new file mode 100644 index 0000000..6d12593 --- /dev/null +++ b/src/app/(routes)/across-settlement/_components/features-section.tsx @@ -0,0 +1,65 @@ +import { + BlocksConnectedIcon, + ChecklistIcon, + CheckmarkIcon, + CrossChainIcon, +} from "@/app/_components/icons"; +import { Text } from "@/app/_components/text"; +import settlement2Image from "@/app/_assets/settlement-2.png"; +import Image from "next/image"; + +const features = [ + { + Icon: , + title: "Cross-Chain Intents Settlement", + body: "Intents are cross-chain limit orders submitted to an RFQ system and fulfilled by relayers with their own capital. Settlement verifies fulfillment and repays relayers. Across’ maximally efficient settlement system reduces cost to relayers, and ultimately, users.", + }, + { + Icon: , + title: "Modular by design", + body: "Across Settlement can accept any intents-based order flow following a standard structure. It seamlessly enables any DEX or application with RFQ order flow to offer best-execution cross-chain swaps from the most competitive network of professional market markets.", + }, + { + Icon: , + title: "aggregated and optimistic verification", + body: "Across verifies fulfillment optimistically. This enables aggregated, off-chain verification and repayment over a 1-2 hour period, amortizing this cost over thousands of orders, resulting in an order of magnitude gas savings vs. per-fill repayment.", + }, + { + Icon: , + title: "Seamless cross-chain management", + body: "Repayment is made on the relayers’ chain of choice, reducing overhead and complexity of managing cross-chain positions. Lower costs for relayers resulting in lower costs for users.", + }, +]; + +export function FeaturesSection() { + return ( +
+ Across settlement layers graphic +
+ + industry-leading performance + + + Superior settlement +
+ Best Execution +
+
+
+ {features.map((feature) => ( +
+ {feature.Icon} + + {feature.title} + + {feature.body} +
+ ))} +
+
+ ); +} diff --git a/src/app/(routes)/across-settlement/_components/get-started-section.tsx b/src/app/(routes)/across-settlement/_components/get-started-section.tsx new file mode 100644 index 0000000..9e1c3c1 --- /dev/null +++ b/src/app/(routes)/across-settlement/_components/get-started-section.tsx @@ -0,0 +1,19 @@ +import Image from "next/image"; + +import { GetStartedSection } from "@/app/_components/get-started-section"; +import settlementLayerImage from "@/app/_assets/settlement-layer.png"; + +export function SettlementGetStartedSection() { + return ( + + } + /> + ); +} diff --git a/src/app/(routes)/across-settlement/_components/hero-section.tsx b/src/app/(routes)/across-settlement/_components/hero-section.tsx new file mode 100644 index 0000000..bb0c8f6 --- /dev/null +++ b/src/app/(routes)/across-settlement/_components/hero-section.tsx @@ -0,0 +1,53 @@ +import Image from "next/image"; + +import { Text } from "@/app/_components/text"; +import { ArrowRightIcon } from "@/app/_components/icons/arrow-right"; +import settlementHeroImage from "@/app/_assets/settlement-hero.png"; +import { INFORMATION_LINKS } from "@/app/_constants"; + +export function HeroSection() { + return ( +
+
+
+ Across settlement graphic +
+
+ + Cross-Chain{" "} + Intents
+ Settlement{" "} + Layer +
+
+ + Efficiently transform RFQ order flow into cross-chain swaps with + Across' intents settlement system. + +
+ + go to docs +
+ +
+
+
+
+
+ ); +} diff --git a/src/app/(routes)/across-settlement/_components/stats-section.tsx b/src/app/(routes)/across-settlement/_components/stats-section.tsx new file mode 100644 index 0000000..01a0f45 --- /dev/null +++ b/src/app/(routes)/across-settlement/_components/stats-section.tsx @@ -0,0 +1,20 @@ +import { StatsRow } from "@/app/_components/stats-row"; + +const statBoxOverrides = { + className: "group-hover:border-purple-100", + titleClassName: "text-purple-100", + dividerClassName: "group-hover:bg-purple-100/[.5]", +}; + +export async function StatsSection() { + return ( +
+ +
+ ); +} diff --git a/src/app/(routes)/across-settlement/page.tsx b/src/app/(routes)/across-settlement/page.tsx index efe1b7c..109a03f 100644 --- a/src/app/(routes)/across-settlement/page.tsx +++ b/src/app/(routes)/across-settlement/page.tsx @@ -1,5 +1,11 @@ import { Metadata } from "next"; +import { HeroSection } from "./_components/hero-section"; +import { StatsSection } from "./_components/stats-section"; +import { FeaturesSection } from "./_components/features-section"; +import { SupportedChainsSection } from "@/app/_components/supported-chains-section"; +import { SettlementGetStartedSection } from "./_components/get-started-section"; + export const metadata: Metadata = { title: "Across Settlement", description: "Interoperability powered by Intents.", @@ -7,8 +13,12 @@ export const metadata: Metadata = { export default function SettlementLanding() { return ( -
-
Across Settlement
+
+ + + + +
); } diff --git a/src/app/_assets/settlement-2.png b/src/app/_assets/settlement-2.png new file mode 100644 index 0000000..996175b Binary files /dev/null and b/src/app/_assets/settlement-2.png differ diff --git a/src/app/_assets/settlement-hero.png b/src/app/_assets/settlement-hero.png new file mode 100644 index 0000000..75b2e93 Binary files /dev/null and b/src/app/_assets/settlement-hero.png differ diff --git a/src/app/_assets/settlement-layer.png b/src/app/_assets/settlement-layer.png new file mode 100644 index 0000000..24aad3f Binary files /dev/null and b/src/app/_assets/settlement-layer.png differ diff --git a/src/app/_components/get-started-section.tsx b/src/app/_components/get-started-section.tsx new file mode 100644 index 0000000..e55fca6 --- /dev/null +++ b/src/app/_components/get-started-section.tsx @@ -0,0 +1,70 @@ +import { ArrowRightIcon } from "@/app/_components/icons"; +import { Text } from "@/app/_components/text"; +import { INFORMATION_LINKS } from "@/app/_constants"; +import { ReactNode } from "react"; +import { twJoin } from "tailwind-merge"; + +const variants = { + teal: { + textClassName: "text-teal-100", + bgClassName: "bg-teal-100", + opacityBgClassName: "bg-teal-100/[.05]", + }, + purple: { + textClassName: "text-purple-100", + bgClassName: "bg-purple-100", + opacityBgClassName: "bg-purple-100/[.05]", + }, +}; + +export function GetStartedSection(props: { + Image: ReactNode; + variant: "teal" | "purple"; +}) { + return ( +
+ {props.Image} + + Ready to get started? + +
+ + + go to docs + + + {/* TODO: Use correct link */} + + + get in touch + +
+ +
+
+
+
+ ); +} diff --git a/src/app/_components/icons/gradient/checkmark.tsx b/src/app/_components/icons/gradient/checkmark.tsx index 3aa7239..6317280 100644 --- a/src/app/_components/icons/gradient/checkmark.tsx +++ b/src/app/_components/icons/gradient/checkmark.tsx @@ -8,6 +8,7 @@ export function CheckmarkIcon(props: SVGProps) { viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" + {...props} > ) { viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg" + {...props} > & { +export type Props = ComponentProps<"div"> & { title: string; titleClassName: string; value: string; diff --git a/src/app/_components/stats-row.tsx b/src/app/_components/stats-row.tsx new file mode 100644 index 0000000..82ca49b --- /dev/null +++ b/src/app/_components/stats-row.tsx @@ -0,0 +1,70 @@ +import { StatBox, Props as StatBoxProps } from "./stat-box"; + +import { getProtocolStats } from "../_lib/scraper"; +import { humanReadableNumber } from "../_lib/format"; + +async function getFormattedStatsData() { + const protocolStats = await getProtocolStats({ + revalidate: 24 * 60 * 60, // Update once a day + }); + + return { + totalVolumeUsd: `$${humanReadableNumber(protocolStats.totalVolumeUsd)}`, + totalDeposits: `${humanReadableNumber(protocolStats.totalDeposits)}`, + avgFillTimeInMinutes: `${protocolStats.avgFillTimeInMinutes < 1 ? "<" : ""} ${Math.max( + protocolStats.avgFillTimeInMinutes, + 1, + )}m`, + bridgeFee: "<$1", + }; +} + +type OverrideStatBoxProps = Pick< + StatBoxProps, + "titleClassName" | "className" | "dividerClassName" +>; + +export async function StatsRow(props: { + volumeBoxProps?: OverrideStatBoxProps; + transactionBoxProps?: OverrideStatBoxProps; + avgFillTimeBoxProps?: OverrideStatBoxProps; + bridgeFeeBoxProps?: OverrideStatBoxProps; +}) { + const formattedStatsData = await getFormattedStatsData(); + return ( +
+ + + + +
+ ); +} diff --git a/src/app/_components/supported-chains-section.tsx b/src/app/_components/supported-chains-section.tsx new file mode 100644 index 0000000..8f92b36 --- /dev/null +++ b/src/app/_components/supported-chains-section.tsx @@ -0,0 +1,87 @@ +import { twMerge } from "@/app/_lib/tw-merge"; +import { + EthereumIcon, + OptimismIcon, + PolygonIcon, + ArbitrumIcon, + BaseIcon, + ZkSyncIcon, +} from "@/app/_components/icons"; +import { Text } from "@/app/_components/text"; + +const chains = [ + { + label: "Ethereum Mainnet", + Icon: EthereumIcon, + }, + { + label: "Optimism", + Icon: OptimismIcon, + }, + { + label: "Polygon Network", + Icon: PolygonIcon, + iconClassName: "h-8 w-9 md:h-10 md:w-10", + }, + { + label: "Arbitrum One", + Icon: ArbitrumIcon, + }, + { + label: "Base", + Icon: BaseIcon, + }, + { + label: "zkSync Era", + Icon: ZkSyncIcon, + }, +]; + +const variants = { + teal: { + textClassName: "text-teal-100", + bgClassName: "bg-teal-100/[.05]", + }, + purple: { + textClassName: "text-purple-100", + bgClassName: "bg-purple-100/[.05]", + }, +}; + +export function SupportedChainsSection(props: { variant: "teal" | "purple" }) { + return ( +
+
+ + well connected + + + Supported Chains + +
+
+ {chains.map((chain, index) => ( +
+
+ +
+ + {chain.label} + +
+ ))} +
+
+ ); +}