Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 48 additions & 15 deletions typescript/site/app/components/CodeSnippet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { useState } from "react";

interface CodeSnippetProps {
code: string;
/** If provided, this value is copied to clipboard instead of `code`. Use to
* show a compact display version while preserving properly-formatted copy. */
copyCode?: string;
title?: string;
description?: string;
}

export function CodeSnippet({ code, title, description }: CodeSnippetProps) {
export function CodeSnippet({ code, copyCode, title, description }: CodeSnippetProps) {
const [copied, setCopied] = useState(false);

const handleCopy = () => {
navigator.clipboard.writeText(code);
navigator.clipboard.writeText(copyCode ?? code);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
Expand All @@ -39,26 +42,58 @@ export function CodeSnippet({ code, title, description }: CodeSnippetProps) {
fill="black"
/>
</svg>
<h3 className="font-mono text-lg font-medium tracking-tight">
{title}
</h3>
<h3 className="font-mono text-lg font-medium tracking-tight">{title}</h3>
</div>
)}

<div className="relative bg-[#F1F1F1] px-3 py-2 mb-4">
<div className="relative bg-[#F1F1F1] px-3 py-2 pb-2 mb-4">
<button
onClick={handleCopy}
className="absolute top-2 right-2 p-1.5 bg-black text-white hover:bg-gray-800 transition-colors focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2"
aria-label={copied ? "Copied to clipboard" : "Copy code"}
>
{copied ? (
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M4 10L8 14L16 6" stroke="white" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
<svg
width="14"
height="14"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M4 10L8 14L16 6"
stroke="white"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
) : (
<svg width="14" height="14" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="6" y="6" width="10" height="12" rx="1" stroke="white" strokeWidth="2" fill="none"/>
<path d="M4 14V3C4 2.44772 4.44772 2 5 2H12" stroke="white" strokeWidth="2" strokeLinecap="round"/>
<svg
width="14"
height="14"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<rect
x="6"
y="6"
width="10"
height="12"
rx="1"
stroke="white"
strokeWidth="2"
fill="none"
/>
<path
d="M4 14V3C4 2.44772 4.44772 2 5 2H12"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
)}
</button>
Expand All @@ -69,7 +104,7 @@ export function CodeSnippet({ code, title, description }: CodeSnippetProps) {
fontFamily: '"DM Mono", monospace',
fontStyle: "normal",
fontWeight: 400,
lineHeight: "20px",
lineHeight: "18px",
letterSpacing: "-0.7px",
}}
>
Expand All @@ -79,9 +114,7 @@ export function CodeSnippet({ code, title, description }: CodeSnippetProps) {
</div>

{description && (
<p className="text-sm text-gray-60 font-mono leading-normal">
{description}
</p>
<p className="text-sm text-gray-60 font-mono leading-normal">{description}</p>
)}
</div>
</div>
Expand Down
14 changes: 11 additions & 3 deletions typescript/site/app/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ export function Footer() {
>
Whitepaper
</Link>
<Link
href="https://docs.google.com/forms/d/e/1FAIpQLSc2rlaeH31rZpJ_RFNL7egxi9fYTEUjW9r2kwkhd2pMae2dog/viewform"
target="_blank"
rel="noopener noreferrer"
className="text-white hover:text-gray-300 text-base focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-offset-2 focus-visible:ring-offset-black"
>
Contact
</Link>
</div>
</nav>
</div>
Expand Down Expand Up @@ -97,8 +105,8 @@ export function Footer() {
{/* Copyright row */}
<div className="flex justify-between items-center">
<p className="text-white/40 text-sm">
While x402 is an open and neutral standard, this website is maintained by
{" "}Coinbase Developer Platform. By using this site, you agree to be bound by the{" "}
While x402 is an open and neutral standard, this website is maintained by Coinbase
Developer Platform. By using this site, you agree to be bound by the{" "}
<Link
href="https://www.coinbase.com/legal/developer-platform/terms-of-service"
target="_blank"
Expand Down Expand Up @@ -128,7 +136,7 @@ export function Footer() {
alt=""
aria-hidden="true"
className="w-full h-auto"
style={{ filter: 'brightness(0.75)' }}
style={{ filter: "brightness(0.75)" }}
/>
</div>
</footer>
Expand Down
2 changes: 1 addition & 1 deletion typescript/site/app/components/HeroIllustration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function HeroIllustration() {
href="https://www.x402.org/protected"
target="_blank"
rel="noopener noreferrer"
className="absolute bottom-[260px] right-[190px] z-10"
className="absolute bottom-[300px] right-[190px] z-10"
>
<Image
src="/images/phone.svg"
Expand Down
52 changes: 39 additions & 13 deletions typescript/site/app/components/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
"use client";

import Link from "next/link";
import { motion } from "motion/react";
import { CodeSnippet } from "./CodeSnippet";
import { HeroIllustration } from "./HeroIllustration";
import { X402Logo } from "./Logo";
import { textStagger, fadeInUp, fadeInFromRight } from "@/lib/animations";
import { BrandSet, StatItem, STATIC_STATS } from "./StatsSection";
import { textStagger, fadeInUp } from "@/lib/animations";

interface HeroSectionProps {
codeSnippet: {
code: string;
copyCode?: string;
title: string;
description: string;
};
}

export function HeroSection({ codeSnippet }: HeroSectionProps) {
return (
<section className="max-w-container mx-auto px-4 sm:px-6 md:px-10 pt-8 md:pt-10 pb-12 sm:pb-16 md:pb-20 overflow-x-clip">
<div className="flex flex-col lg:flex-row gap-8 md:gap-12 lg:gap-8 items-start lg:items-center">
<section className="hero-section max-w-container mx-auto px-4 sm:px-6 md:px-10 pt-8 md:pt-10 pb-12 sm:pb-16 md:pb-20 overflow-x-clip">
<div className="hero-flex-row flex flex-col lg:flex-row gap-8 md:gap-12 lg:gap-8">
{/* Animated left column */}
<motion.div
className="flex-1 flex flex-col gap-4"
className="hero-left-col flex-1 min-w-0 flex flex-col gap-4"
variants={textStagger}
initial="initial"
animate="animate"
Expand All @@ -32,7 +35,7 @@ export function HeroSection({ codeSnippet }: HeroSectionProps) {

<motion.p
variants={fadeInUp}
className="text-base sm:text-lg font-medium leading-relaxed max-w-[600px]"
className="text-sm sm:text-base font-medium text-gray-70 leading-relaxed max-w-[600px]"
>
x402 is an open, neutral standard for internet-native payments. It absolves the
Internet's original sin by natively making payments possible between clients and
Expand All @@ -44,20 +47,43 @@ export function HeroSection({ codeSnippet }: HeroSectionProps) {
<CodeSnippet
title={codeSnippet.title}
code={codeSnippet.code}
copyCode={codeSnippet.copyCode}
description={codeSnippet.description}
/>
</motion.div>

<motion.div variants={fadeInUp}>
<Link href="/ecosystem" className="block" aria-label="View ecosystem partners">
<p className="text-xs font-medium text-gray-40 uppercase tracking-wide mb-4">
Trusted by
</p>
<div className="overflow-hidden [--gap:2.5rem] sm:[--gap:3rem] md:[--gap:4rem] [mask-image:linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]">
<div className="flex [gap:var(--gap)]">
{Array.from({ length: 4 }).map((_, i) => (
<BrandSet key={i} />
))}
</div>
</div>
</Link>
</motion.div>

{/* Stats pinned to the bottom of the left column — desktop only */}
<div className="mt-auto hidden xl:block pt-8" aria-label="Platform statistics">
<p className="text-xs font-medium text-gray-40 uppercase tracking-wide mb-4">
Last 30 days
</p>
<div className="flex flex-wrap items-end gap-6 sm:gap-8 md:gap-16 lg:gap-20">
<StatItem value={STATIC_STATS.transactions} label="Transactions" />
<StatItem value={STATIC_STATS.volume} label="Volume" />
<StatItem value={STATIC_STATS.buyers} label="Buyers" />
<StatItem value={STATIC_STATS.sellers} label="Sellers" />
</div>
</div>
</motion.div>

{/* Animated right column - only show at xl (1280px+) where there's room */}
<motion.div
className="relative hidden xl:block flex-shrink-0 xl:w-[720px]"
variants={fadeInFromRight}
initial="initial"
animate="animate"
>
<div className="hero-illustration-col">
<HeroIllustration />
</motion.div>
</div>
</div>
</section>
);
Expand Down
Loading
Loading