Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fbf5d9b
Add v2 homepage focused on Bitcoin gift cards
May 5, 2026
4ef4836
fix(homepage): resolve lint errors blocking CI
orveth May 5, 2026
64c6ad5
feat(auth): skip splash for logged-out users on protected paths
orveth May 5, 2026
9ffef9a
chore(homepage): remove never-deployed v1 directory and route
orveth May 5, 2026
facb784
chore(homepage): drop /home meta override so it inherits root.tsx OG …
orveth May 5, 2026
a9c5a55
fix(homepage): eliminate flash at end of hero card transition
orveth May 5, 2026
117c867
fix(prerender): disable Suspense outlining to prevent fallback flash
ditto-agent May 8, 2026
8ab4d7b
test(vercel): rewrite prerendered paths to their static HTML files
ditto-agent May 8, 2026
97a80ef
test(vercel): add cleanUrls to try unblocking prerender route serving
ditto-agent May 8, 2026
f8bcd12
test(vercel): try redirect for /home to diagnose framework-preset int…
ditto-agent May 8, 2026
621f23b
test(vercel): drop cleanUrls so redirect doesn't loop with index.html…
ditto-agent May 8, 2026
d7de0b1
fix(prerender): also disable Suspense outlining at runtime SSR
ditto-agent May 8, 2026
ac35288
refactor(homepage): convert custom CSS to Tailwind utilities (preview)
orveth May 12, 2026
ff5b688
fix(homepage): restore Kode Mono on font-mono inside marketing tree
orveth May 13, 2026
e29ef9a
fix(homepage): repair broken font-family arbitrary values for Tailwin…
orveth May 13, 2026
251654a
fix(homepage): drop marketing-anchor color-inherit override
orveth May 13, 2026
7509963
feat(homepage): always show motion, drop prefers-reduced-motion gates
orveth May 13, 2026
9189132
fix(homepage): drop motion-safe gates and restore nav button borders
orveth May 13, 2026
3f3c8b5
fix(homepage): pre-decode gift card images to eliminate transition flash
orveth May 13, 2026
5455d18
feat(homepage): replace hero pixel-wipe with stacked crossfade
orveth May 13, 2026
e41f66a
fix(homepage): preload marketing fonts to eliminate FOUT-induced layo…
May 13, 2026
697fdb7
fix(homepage): make spend QR responsive and harden iOS clip
orveth May 14, 2026
6d978e6
feat(homepage): refresh hero cards to live discoverable set
orveth May 14, 2026
78dab61
fix(homepage): center QR vertically in iOS WebKit via flex + explicit…
orveth May 14, 2026
ca49279
fix(homepage): expand spend PAID overlay to cover entire QR area
orveth May 14, 2026
f1c58e0
fix(homepage): bound spend QR SVG to PAID overlay's inset to prevent …
orveth May 14, 2026
d6ce02e
fix(homepage): extend spend PAID overlay bottom to cover iOS QR overflow
orveth May 14, 2026
d00ef27
fix(homepage): clip spend QR via wrapper div + restore symmetric squa…
orveth May 14, 2026
e06a42a
feat(homepage): swap merchants contact mailto for waitlist signup link
orveth May 14, 2026
ab6b851
feat(homepage): hero pixel-mask reveal transition
orveth May 14, 2026
712b5b1
fix(homepage): include incoming card inside isolated dissolve wrapper
orveth May 14, 2026
aa63ac8
fix(homepage): pixel reveal via SVG pattern fill instead of destinati…
orveth May 14, 2026
fd853e9
feat(homepage): smooth-scroll to top when AGICASH logo clicked
orveth May 14, 2026
af1b491
fix(homepage): spread hero pixel-cell flips for distinct timing
orveth May 14, 2026
c87c3dd
fix(homepage): scroll the marketing container, not the window, on log…
orveth May 14, 2026
db5a757
feat(homepage): randomize hero pixel-reveal cell order per transition
orveth May 14, 2026
a0fe668
chore(homepage): update merchants CTA copy to "join the waitlist"
orveth May 14, 2026
c6ea314
feat(homepage): link hero card labels to merchant sites + rename Mari…
orveth May 14, 2026
0147394
fix(homepage): dissolve outgoing card on top of static incoming to el…
orveth May 14, 2026
d91cafc
fix(homepage): convert section title headings to real anchor tags
orveth May 14, 2026
c9d771a
refactor(homepage): drop dead code and trim redundant hero state
gudnuf May 14, 2026
c739f85
refactor(homepage): move section anchors to labels + harden hero pixe…
gudnuf May 14, 2026
9e0860a
fix(homepage): repair iOS hero dissolve + land hash navs at section top
gudnuf May 14, 2026
b002290
docs: agicash Rust SDK design spec
gudnuf May 14, 2026
a7478ea
docs: add multi-device, concurrency, and TDD slicing to Rust SDK spec
gudnuf May 14, 2026
1cb092b
docs: simplify Spark provider boundary in Rust SDK spec
gudnuf May 14, 2026
bb19b8b
perf(homepage): preload pubkey hero card to unblock LCP
gudnuf May 14, 2026
929a118
chore: remove agicash rust SDK design doc
gudnuf May 14, 2026
4a36668
fix(homepage): close section anchor scroll-margin gap to nav border
orveth May 15, 2026
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
1 change: 1 addition & 0 deletions app/assets/btcpay-logo.svg
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if not already done, we should check dimensions that we need (not to have bigger images than we actually need) and if we can compress (change image type if needed) these images to reduce the size

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions app/assets/shopify-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions app/assets/square-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { RenderToPipeableStreamOptions } from 'react-dom/server';
import { renderToPipeableStream } from 'react-dom/server';
import type { EntryContext, RouterContextProvider } from 'react-router';
import { ServerRouter } from 'react-router';
import { PRERENDERED_PATHS } from './prerender-paths';

export const streamTimeout = 5_000;

Expand All @@ -31,6 +32,20 @@ function handleRequest(
let shellRendered = false;
const userAgent = request.headers.get('user-agent');

// react-router's prerender constructs requests with no user-agent header.
const isBuildTimePrerender = !userAgent;

// Vercel currently doesn't serve prerendered HTML as static files when
// ssr:true (remix-run/react-router#14281) — requests for these paths
// fall through to the SSR function. Match them here so the outlining
// override below applies at runtime SSR too. Once the upstream adapter
// bug is fixed, this path-list check can be dropped; the no-UA check
// alone will cover real prerender invocations.
const pathname = new URL(request.url).pathname.replace(/\/+$/, '') || '/';
const isPrerenderRoute = PRERENDERED_PATHS.includes(pathname);

const shouldDisableOutlining = isBuildTimePrerender || isPrerenderRoute;

// Ensure requests from bots and SPA Mode renders wait for all content to load before responding
// https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
const readyOption: keyof RenderToPipeableStreamOptions =
Expand All @@ -41,6 +56,29 @@ function handleRequest(
const { pipe, abort } = renderToPipeableStream(
<ServerRouter context={routerContext} url={request.url} />,
{
// React's streaming SSR uses progressiveChunkSize (default 12.8 KB) to
// optimize first paint: when the rendered HTML exceeds this threshold,
// React "outlines" the next Suspense boundary — it writes the fallback
// into the initial shell and emits the actual content later as a
// hidden <template> that an inline <script> swaps in. The server
// flushes the shell immediately and streams the rest, so the browser
// starts painting (with the fallback) before the full response is
// ready.
//
// This trades a fallback flash for faster first paint. It's a real
// win for streaming SSR with slow data — a skeleton beats a blank
// page. But for prerender-eligible routes it's pure cost: when
// served as static (the intent), the file is buffered and there's
// no opportunity for early flushing — the fallback-then-swap dance
// still happens in the file, so the user sees a fallback flash on
// first paint with nothing gained. When falling through to runtime
// SSR (current Vercel behavior, see above), the same flash returns
// for a different reason but the same fix applies. Setting an
// effectively-infinite threshold disables outlining for these
// requests, so the content renders inline in the shell.
progressiveChunkSize: shouldDisableOutlining
? Number.POSITIVE_INFINITY
: undefined,
[readyOption]() {
shellRendered = true;
const body = new PassThrough();
Expand Down
34 changes: 34 additions & 0 deletions app/features/homepage/components/join-beta-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useQuery } from '@tanstack/react-query';
import { Link, useLocation } from 'react-router';
import { authQueryOptions } from '~/features/user/auth';
import { cn } from '~/lib/utils';

type JoinBetaButtonProps = {
size?: 'default' | 'lg';
className?: string;
};

export function JoinBetaButton({
size = 'default',
className,
}: JoinBetaButtonProps) {
const location = useLocation();
const { data: authState } = useQuery(authQueryOptions());
const isLoggedIn = authState?.isLoggedIn ?? false;

const sizeClasses =
size === 'lg' ? 'h-12 px-7 text-base' : 'h-10 px-5 text-sm';

return (
<Link
to={isLoggedIn ? '/' : { ...location, pathname: '/signup' }}
className={cn(
'inline-flex items-center justify-center rounded-md border border-[color:var(--mk-brand)] bg-[color:var(--mk-brand)] font-medium font-mono text-[#04080f] tracking-wide transition-[background-color,color] duration-200 hover:bg-transparent hover:text-[color:var(--mk-brand)] focus-visible:outline-2 focus-visible:outline-[color:var(--mk-brand)] focus-visible:outline-offset-2',
sizeClasses,
className,
)}
>
{isLoggedIn ? 'Go to Wallet' : 'Get Started'}
</Link>
);
}
24 changes: 24 additions & 0 deletions app/features/homepage/components/marketing-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { HTMLAttributes, ReactNode } from 'react';
import { cn } from '~/lib/utils';

type MarketingCardProps = HTMLAttributes<HTMLDivElement> & {
children: ReactNode;
};

export function MarketingCard({
children,
className,
...props
}: MarketingCardProps) {
return (
<div
className={cn(
'rounded-2xl border border-[color:var(--mk-border)] bg-[color:var(--mk-bg-card)] p-7 transition-colors duration-200 hover:border-[color:var(--mk-border-bright)] md:p-9',
className,
)}
{...props}
>
{children}
</div>
);
}
58 changes: 58 additions & 0 deletions app/features/homepage/components/marketing-nav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router';
import logoUrl from '~/assets/full_logo.png';
import { authQueryOptions } from '~/features/user/auth';

const navBtnBase =
'inline-flex h-9 items-center justify-center whitespace-nowrap rounded-full border px-4 [font-family:var(--mk-font-mono)] text-xs tracking-[0.04em] transition-[background-color,color,border-color] duration-200 md:h-[38px] md:px-5 md:text-[13px]';

const loginBtn =
'border-[color:var(--mk-brand)] bg-transparent text-[color:var(--mk-brand)] hover:bg-[rgba(0,212,255,0.08)]';

const signupBtn =
'border-[color:var(--mk-brand)] bg-[color:var(--mk-brand)] text-[#04080f] hover:bg-transparent hover:text-[color:var(--mk-brand)]';

export function MarketingNav() {
const { data: authState } = useQuery(authQueryOptions());
const isLoggedIn = authState?.isLoggedIn ?? false;

return (
<header className="sticky top-0 z-50 w-full border-[color:var(--mk-border)] border-b bg-[rgba(4,8,15,0.78)] backdrop-blur-[14px] backdrop-saturate-[140%]">
<div className="flex w-full items-center justify-between gap-3 px-5 py-[14px] md:px-8 md:py-4">
<Link
to="/home"
className="inline-flex items-center"
aria-label="Agicash"
onClick={() =>
document
.querySelector('.marketing')
?.scrollTo({ top: 0, behavior: 'smooth' })
}
>
<img
src={logoUrl}
alt="Agicash"
className="block h-[22px] w-auto opacity-90 md:h-[26px]"
/>
</Link>

<nav className="flex items-center gap-2">
{isLoggedIn ? (
<Link to="/" className={`${navBtnBase} ${signupBtn}`}>
Go to Wallet
</Link>
) : (
<>
<Link to="/login" className={`${navBtnBase} ${loginBtn}`}>
Log in
</Link>
<Link to="/signup" className={`${navBtnBase} ${signupBtn}`}>
Sign up
</Link>
</>
)}
</nav>
</div>
</header>
);
}
30 changes: 30 additions & 0 deletions app/features/homepage/components/section-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { cn } from '~/lib/utils';

type SectionLabelProps = {
children: string;
className?: string;
href?: string;
};

export function SectionLabel({ children, className, href }: SectionLabelProps) {
const labelClasses = cn(
'translate-y-2 text-left font-mono text-[color:var(--mk-text-muted)] text-xs uppercase tracking-[0.12em]',
className,
);
const content = (
<>
<span aria-hidden="true">{'> '}</span>
{children}
</>
);

if (href) {
return (
<a href={href} className={cn('block w-fit', labelClasses)}>
{content}
</a>
);
}

return <div className={labelClasses}>{content}</div>;
}
30 changes: 30 additions & 0 deletions app/features/homepage/components/section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { HTMLAttributes, ReactNode } from 'react';
import { cn } from '~/lib/utils';

type SectionProps = HTMLAttributes<HTMLElement> & {
id?: string;
children: ReactNode;
hairline?: boolean;
};

export function Section({
id,
children,
hairline = true,
className,
...props
}: SectionProps) {
return (
<section
id={id}
className={cn(
'relative w-full px-5 py-20 md:px-8 md:py-32',
hairline && 'border-[color:var(--mk-border)] border-t',
className,
)}
{...props}
>
<div className="mx-auto w-full max-w-6xl">{children}</div>
</section>
);
}
28 changes: 28 additions & 0 deletions app/features/homepage/marketing-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { MarketingNav } from './components/marketing-nav';
import { BuySection } from './sections/buy-section';
import { CtaSection } from './sections/cta-section';
import { FooterSection } from './sections/footer-section';
import { HeroSection } from './sections/hero-section';
import { MerchantsSection } from './sections/merchants-section';
import { SendSection } from './sections/send-section';
import { SpendSection } from './sections/spend-section';
import { WalletSection } from './sections/wallet-section';
import './styles.css';

export function MarketingPage() {
return (
<div className="marketing scrollbar-none h-dvh overflow-y-auto overflow-x-hidden bg-[color:var(--mk-bg)] text-[color:var(--mk-text)] [font-family:var(--mk-font-display)]">
<MarketingNav />
<main>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have anchors to each of these sections so user can share the link to specific section

<HeroSection />
<BuySection />
<SendSection />
<SpendSection />
<WalletSection />
<CtaSection />
<MerchantsSection />
</main>
<FooterSection />
</div>
);
}
Loading
Loading