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
218 changes: 123 additions & 95 deletions apps/web/src/app/globals.css

Large diffs are not rendered by default.

50 changes: 28 additions & 22 deletions apps/web/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type { Metadata, Viewport } from 'next'
import { Fraunces, Instrument_Sans, IBM_Plex_Mono } from 'next/font/google'
import Image from 'next/image'
import Link from 'next/link'
import { Analytics } from '@vercel/analytics/next'
import { AppProviders } from './providers'
import { Navbar } from '@/components/Navbar'
import { OnboardingTutorial } from '@/components/OnboardingTutorial'
import './globals.css'

const fontDisplay = Fraunces({ subsets: ['latin'], weight: ['500', '600'], style: ['normal', 'italic'], variable: '--font-display', display: 'swap' })
const fontSans = Instrument_Sans({ subsets: ['latin'], weight: ['400', '500', '600'], variable: '--font-sans', display: 'swap' })
const fontMono = IBM_Plex_Mono({ subsets: ['latin'], weight: ['400', '500'], variable: '--font-mono', display: 'swap' })

export const metadata: Metadata = {
title: { default: 'PotBot POTFolio: any basket of Solana assets as one liquid token', template: '%s · PotBot' },
description: 'Create a portfolio, hold the token. Deposit USDC, get one token that mirrors the whole basket, redeem any time for the assets or USDC. Nobody can withdraw the assets.',
description: 'Creator ETFs without the exit liquidity. Put up to 5 assets in a Pot, hold one token, leave any time. Nobody can reach anyone else\'s money.',
metadataBase: new URL('https://potbot.fun'),
alternates: { canonical: '/' },
openGraph: {
title: 'PotBot POTFolio: any basket of Solana assets as one liquid token',
description: 'Create a portfolio, hold the token. Exit any time, in assets or USDC. Live on Solana devnet.',
description: 'Creator ETFs without the exit liquidity. Up to 5 assets in a Pot, one token, exit any time.',
url: 'https://potbot.fun',
siteName: 'PotBot',
type: 'website',
Expand All @@ -23,7 +29,7 @@ export const metadata: Metadata = {
site: '@PotBot_sol',
creator: '@CryptoYDao',
title: 'PotBot POTFolio: any basket of Solana assets as one liquid token',
description: 'Create a portfolio, hold the token. Exit any time, in assets or USDC. Live on Solana devnet.',
description: 'Creator ETFs without the exit liquidity. Up to 5 assets in a Pot, one token, exit any time.',
},
manifest: '/manifest.json',
appleWebApp: { capable: true, statusBarStyle: 'black-translucent', title: 'PotBot' },
Expand All @@ -38,8 +44,8 @@ export const viewport: Viewport = {
maximumScale: 1,
viewportFit: 'cover',
themeColor: [
{ media: '(prefers-color-scheme: dark)', color: '#0D1117' },
{ media: '(prefers-color-scheme: light)', color: '#f5f7fa' },
{ media: '(prefers-color-scheme: dark)', color: '#14120F' },
{ media: '(prefers-color-scheme: light)', color: '#F6F1E8' },
],
}

Expand Down Expand Up @@ -76,9 +82,9 @@ const FOOTER_COLUMNS: { title: string; links: { label: string; href: string; ext
title: 'Project',
links: [
{ label: 'Roadmap', href: '/roadmap' },
{ label: 'Mainnet plan', href: '/mainnet' },
{ label: 'For the judges', href: '/worldsfair' },
{ label: 'Vaults (in development)', href: '/vaults' },
{ label: 'Security', href: '/mainnet' },
{ label: 'Status', href: '/status' },
{ label: "World's Fair", href: '/worldsfair' },
{ label: 'For AI agents', href: '/for-agents' },
],
},
Expand All @@ -95,7 +101,7 @@ const FOOTER_COLUMNS: { title: string; links: { label: string; href: string; ext

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" suppressHydrationWarning className={`${fontDisplay.variable} ${fontSans.variable} ${fontMono.variable}`}>
<head>
<script dangerouslySetInnerHTML={{ __html: THEME_BOOTSTRAP }} />
</head>
Expand All @@ -117,17 +123,17 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<div className="grid gap-8 mb-8 sm:grid-cols-[1.4fr_1fr_1fr_1fr]">
<div>
<Link href="/" className="flex items-center gap-2">
<span className="text-xl">🪴</span>
<span className="font-bold text-white">Pot<span className="text-pot-green">Bot</span></span>
<Image src="/garden/pink-1.png" alt="" width={24} height={24} className="h-6 w-6 object-contain" />
<span className="font-display text-lg font-semibold text-white">PotBot</span>
</Link>
<p className="mt-3 max-w-xs text-xs leading-relaxed text-pot-muted">
Any basket of Solana assets as one liquid token. Create a portfolio, hold the token. Live on devnet, built in Amsterdam.
<p className="mt-3 max-w-xs text-[13px] leading-relaxed text-pot-muted">
Your portfolio. One token. Built in Amsterdam on Solana.
</p>
</div>
{FOOTER_COLUMNS.map((col) => (
<nav key={col.title} aria-label={col.title}>
<p className="mb-2 text-[11px] font-semibold uppercase tracking-widest text-pot-muted/70">{col.title}</p>
<ul className="space-y-1.5 text-xs text-pot-muted">
<p className="mb-2 font-mono text-[11px] uppercase tracking-[0.14em] text-pot-muted">{col.title}</p>
<ul className="space-y-1.5 text-[13px] text-pot-muted">
{col.links.map(({ label, href, external }) => (
<li key={label}>
{external ? (
Expand All @@ -143,26 +149,26 @@ export default function RootLayout({ children }: { children: React.ReactNode })
</div>

{/* Disclaimers */}
<div className="border-t border-pot-border/30 pt-5 space-y-1.5 text-[11px] text-pot-muted leading-relaxed">
<div className="border-t border-pot-border/30 pt-5 space-y-1.5 text-[12px] text-pot-muted leading-relaxed">
<p>
<span className="font-semibold text-pot-muted/70">Non-custodial:</span>{' '}
<span className="font-semibold text-white">Non-custodial:</span>{' '}
PotBot is non-custodial software. Assets sit in program-owned accounts with no withdraw instruction; the index token is the only claim on them.
</p>
<p>
<span className="font-semibold text-pot-muted/70">Risk:</span>{' '}
<span className="font-semibold text-white">Risk:</span>{' '}
Crypto assets are volatile. You may lose some or all of the funds you deposit. Past performance does not predict future results.
</p>
<p>
<span className="font-semibold text-pot-muted/70">Not advice:</span>{' '}
<span className="font-semibold text-white">Not advice:</span>{' '}
PotBot is not investment advice. Holders make their own decisions; nothing here is a recommendation to buy or sell any asset.
</p>
<p>
<span className="font-semibold text-pot-muted/70">Jurisdictions:</span>{' '}
<span className="font-semibold text-white">Jurisdictions:</span>{' '}
Use of PotBot is restricted in certain jurisdictions. By using this app you confirm compliance with your local laws.{' '}
<Link href="/faq" className="underline hover:text-white">See FAQ</Link>
</p>
<p className="pt-1 text-pot-border">
© 2026 PotBot · Built on Solana · Open source ·{' '}
<p className="pt-1 text-pot-muted/70">
© 2026 PotBot · Amsterdam · Built on Solana · Open source ·{' '}
<a href="https://github.com/YD811/potbot-v2" target="_blank" rel="noopener noreferrer" className="underline hover:text-white">
View code
</a>
Expand Down
6 changes: 3 additions & 3 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import LandingPage from '@/components/LandingPage'
import { LandingV3 } from '@/components/v3/LandingV3'

/** Home is always the landing. The vault dashboard lives on /vaults and /my-pots. */
/** Home is the v3 landing (design system v3, Oct 2026). The previous landing stays in components/LandingPage.tsx. */
export default function HomePage() {
return <LandingPage />
return <LandingV3 />
}
57 changes: 20 additions & 37 deletions apps/web/src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

import { useState } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import { usePathname } from 'next/navigation'
import { useWallet } from '@solana/wallet-adapter-react'
import { useQuery } from '@tanstack/react-query'
import { useSolPrice } from '@/lib/prices'
import { healthApi } from '@/lib/api-client'
import { ThemeToggle } from '@/components/ThemeToggle'
import { ConnectButton } from '@/components/ConnectButton'
import { SeasonPrizePoolModal } from '@/components/SeasonPrizePoolModal'
Expand All @@ -16,47 +15,32 @@ function LivePriceTicker() {
const { price: solPrice } = useSolPrice()
const [showSeasonModal, setShowSeasonModal] = useState(false)

const { data: health, isError: apiDown } = useQuery({
queryKey: ['api-health'],
queryFn: healthApi.check,
refetchInterval: 30_000,
retry: 1,
staleTime: 25_000,
})

return (
<div className="border-b border-pot-border/40 bg-pot-dark/60">
<div className="border-b border-pot-border/60 bg-pot-dark/60">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 h-7 flex items-center gap-4">
{solPrice != null && (
<div className="flex items-center gap-1.5 text-[11px]">
<span className="text-pot-muted">◎</span>
<span className="font-mono font-semibold text-white">
<span className="font-mono font-medium tabular-nums text-white">
${solPrice.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
</span>
<span className="text-pot-muted">SOL</span>
</div>
)}
<div className="w-px h-3 bg-pot-border" />
<div className="flex items-center gap-1.5 text-[11px]">
<span className={`w-1.5 h-1.5 rounded-full ${apiDown ? 'bg-red-500' : 'bg-pot-green animate-pulse'}`} />
<span className={apiDown ? 'text-red-400/80' : 'text-pot-green'}>
{apiDown ? 'Demo Mode' : 'Live'}
</span>
</div>
{/* Season 1 ticker — clickable, opens prize-pool modal */}
<div className="hidden sm:flex items-center gap-1.5 text-[11px]">
<span className="text-pot-muted">|</span>
<span className="w-px h-3 bg-pot-border" />
<button
onClick={() => setShowSeasonModal(true)}
className="text-pot-accent hover:text-white hover:underline underline-offset-2 transition cursor-pointer"
className="text-pot-muted hover:text-white hover:underline underline-offset-2 transition cursor-pointer"
aria-label="Open Season 1 prize pool details"
>
🌱 Season 1: The Garden
Season 1: The Garden
</button>
</div>
<div className="ml-auto flex items-center gap-3 text-[11px] text-pot-muted">
<Link href="/for-agents" className="hover:text-pot-green transition hidden sm:block">
🤖 For AI Agents
<Link href="/for-agents" className="hover:text-white transition hidden sm:block">
For AI agents
</Link>
</div>
</div>
Expand All @@ -71,9 +55,8 @@ function LivePriceTicker() {
// as a tab inside /pots. FAQ lives in the footer only.
const NAV_LINKS: { href: string; emoji: string; term: string; hot?: boolean }[] = [
{ href: '/portfolios', emoji: '', term: 'POTFolios' },
{ href: '/portfolios/new', emoji: '+', term: 'Create' },
{ href: '/portfolios/new', emoji: '', term: 'Create' },
{ href: '/learn', emoji: '', term: 'Learn' },
{ href: '/roadmap', emoji: '', term: 'Roadmap' },
{ href: '/worldsfair', emoji: '', term: "World's Fair", hot: true },
]

Expand All @@ -99,15 +82,15 @@ export function Navbar() {
<LivePriceTicker />
<div className="mx-auto flex h-14 max-w-7xl items-center justify-between px-4 sm:px-6 lg:px-8">
<Link href="/" className="flex items-center gap-2 group shrink-0">
<span className="text-2xl group-hover:animate-float">🪴</span>
<span className="text-xl font-bold text-white">Pot<span className="text-pot-green">Bot</span></span>
<Image src="/garden/pink-1.png" alt="" width={28} height={28} className="h-7 w-7 object-contain" priority />
<span className="font-display text-[22px] font-semibold tracking-tight text-white">PotBot</span>
</Link>

<div className="hidden sm:flex items-center gap-1 text-sm font-medium">
{NAV_LINKS.map((link) => (
<Link key={link.href} href={link.href} className={`px-3 py-1.5 rounded-lg transition ${
isActive(link.href) ? 'text-white bg-pot-card border border-pot-border' : link.hot ? 'text-pot-green hover:bg-pot-green/10 ring-1 ring-pot-green/40 shadow-[0_0_14px_rgba(20,241,149,0.25)]' : 'text-gray-400 hover:text-white hover:bg-pot-card/50'
}`}>{link.hot && <span className="mr-1.5 inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-pot-green align-middle" />}{navLabel(link)}</Link>
<Link key={link.href} href={link.href} className={`px-3.5 py-1.5 rounded-full transition ${
isActive(link.href) ? 'text-white bg-pot-card border border-pot-border' : 'text-pot-muted hover:text-white'
}`}>{link.hot && <span className="mr-1.5 inline-block h-1.5 w-1.5 rounded-full bg-pot-green align-middle" />}{navLabel(link)}</Link>
))}
</div>

Expand All @@ -117,7 +100,7 @@ export function Navbar() {
{showWaitlistCta && (
<Link
href="/signup"
className="hidden sm:inline-flex items-center gap-1.5 rounded-full bg-pot-green px-4 py-1.5 text-sm font-bold text-pot-dark shadow-[0_0_18px_rgba(20,241,149,0.35)] hover:brightness-110 transition whitespace-nowrap"
className="hidden sm:inline-flex items-center gap-1.5 rounded-full border border-pot-border px-4 py-1.5 text-sm font-semibold text-white hover:border-pot-muted transition whitespace-nowrap"
>
Waitlist
</Link>
Expand Down Expand Up @@ -147,25 +130,25 @@ export function Navbar() {
<Link
href="/signup"
onClick={() => setMenuOpen(false)}
className="flex items-center justify-center gap-1.5 rounded-xl bg-pot-green px-4 py-3 text-sm font-bold text-pot-dark shadow-[0_0_18px_rgba(20,241,149,0.35)] hover:brightness-110 transition mb-2"
className="flex items-center justify-center gap-1.5 rounded-full bg-pot-accent px-4 py-3 text-sm font-semibold text-pot-dark hover:brightness-110 transition mb-2"
>
Join the waitlist
</Link>
)}

<Link href="/" onClick={() => setMenuOpen(false)}
className={`block px-4 py-2.5 rounded-xl text-sm font-medium transition ${
isActive('/') ? 'text-white bg-pot-card border border-pot-border' : 'text-gray-400 hover:text-white hover:bg-pot-card/50'
isActive('/') ? 'text-white bg-pot-card border border-pot-border' : 'text-pot-muted hover:text-white hover:bg-pot-card/50'
}`}>Home</Link>
{NAV_LINKS.map((link) => (
<Link key={link.href} href={link.href} onClick={() => setMenuOpen(false)}
className={`block px-4 py-2.5 rounded-xl text-sm font-medium transition ${
isActive(link.href) ? 'text-white bg-pot-card border border-pot-border' : 'text-gray-400 hover:text-white hover:bg-pot-card/50'
isActive(link.href) ? 'text-white bg-pot-card border border-pot-border' : 'text-pot-muted hover:text-white hover:bg-pot-card/50'
}`}>{navLabel(link)}</Link>
))}
<div className="pt-2 border-t border-pot-border/50 grid grid-cols-2 gap-x-2">
{[['/faq', 'FAQ'], ['/mainnet', 'Mainnet'], ['/worldsfair', 'For the judges'], ['/vaults', 'Vaults (in development)']].map(([href, label]) => (
<Link key={href} href={href} onClick={() => setMenuOpen(false)} className="block px-4 py-2 text-sm text-pot-muted hover:text-pot-green transition">{label}</Link>
{[['/faq', 'FAQ'], ['/mainnet', 'Security'], ['/roadmap', 'Roadmap'], ['/status', 'Status']].map(([href, label]) => (
<Link key={href} href={href} onClick={() => setMenuOpen(false)} className="block px-4 py-2 text-sm text-pot-muted hover:text-white transition">{label}</Link>
))}
</div>
</div>
Expand Down
Loading
Loading