v2 homepage: Bitcoin gift cards focus#1050
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
| import { cn } from '~/lib/utils'; | ||
|
|
||
| type JoinBetaButtonProps = { | ||
| size?: 'default' | 'lg'; |
There was a problem hiding this comment.
lets remove the size prop its unused we only use lg.
| @@ -0,0 +1,1545 @@ | |||
| @import url("https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@400,500,600,700,800&display=swap"); | |||
There was a problem hiding this comment.
lets make sure that we handle this font the same way as the rest, like should we put it in a links function in the home route?
There was a problem hiding this comment.
why do we even have different fonts for landing page? is that common thing to do to have different fonts for different parts of the app?
There was a problem hiding this comment.
I used the same font for the headings, but for the longer text I wanted something more readable.
| @@ -0,0 +1,1545 @@ | |||
| @import url("https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@400,500,600,700,800&display=swap"); | |||
There was a problem hiding this comment.
why do we even have different fonts for landing page? is that common thing to do to have different fonts for different parts of the app?
| } | ||
| } | ||
|
|
||
| .marketing .mk-cta { |
There was a problem hiding this comment.
why do we even have these classes like mk-cta, pay-qr-wrap, etc.? @gudnuf isn't the recommended approach with tailwind different?
| } | ||
|
|
||
| function QrPattern() { | ||
| const grid = useMemo(() => buildQrPattern(QR_SIZE, 4242), []); |
There was a problem hiding this comment.
why are we building this pattern dynamically? cant we just hardcode the value?
There was a problem hiding this comment.
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
| return ( | ||
| <div className="marketing"> | ||
| <MarketingNav /> | ||
| <main> |
There was a problem hiding this comment.
we should have anchors to each of these sections so user can share the link to specific section
| * these routes — Vercel currently doesn't serve them as static files when | ||
| * `ssr:true`, see remix-run/react-router#14281). | ||
| */ | ||
| export const PRERENDERED_PATHS = ['/terms', '/privacy', '/mint-risks', '/home']; |
There was a problem hiding this comment.
#1047 changed the terms routes to /terms/wallet, /terms/mint, /privacy/wallet, /privacy/mint
Pretty sure const isPrerenderRoute = PRERENDERED_PATHS.includes(pathname); won't catch any of these
| (preset): preset is Preset => Boolean(preset), | ||
| ), | ||
| async prerender() { | ||
| return ['/terms', '/privacy', '/mint-risks', '/home']; |
There was a problem hiding this comment.
oh I guess this PR is still out of date, master should have all of these added in #1047
96f656f to
e2cae69
Compare
Replaces the placeholder Coming Soon page with a real homepage focused on Bitcoin gift cards. Five sections plus consumer + merchant CTAs and a brand-foot AGICASH wordmark. - /home → v2 (default) - /home-v1 → v1 archived for reference (noindex) - Sticky nav with Log in / Sign up - Cabinet Grotesk + Kode Mono + Teko typography - Square / BTCPay Server / Shopify partner logos - Pixelated wipe transitions on the hero carousel - Interactive Agicash → Cash App buy flow with slide / fade transitions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Mark decorative SVGs in buy-section as aria-hidden (matches spend/hero pattern). - Suppress noArrayIndexKey on the static QR grid — pattern is deterministic and never reorders. - Drop unused PIXEL_CELLS constant in hero-section. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously, a logged-out user cold-loading `/` (or any protected path) would see the AGICASH logo splash flash for ~150ms before being redirected to `/home`. The splash is `_protected.tsx`'s HydrateFallback, rendered while React Router waits for `routeGuardMiddleware` to resolve and throw a redirect. Add a synchronous check in `entry.client.tsx` that runs before hydration: if the current path is non-public and `localStorage` has neither `access_token` nor `refresh_token`, redirect to `/home` (with the same `redirectTo` query param the middleware would set) and halt module evaluation. Public paths and authenticated paths fall through unchanged. The middleware redirect at `_protected.tsx:184-191` is intentionally preserved. It is the safety net for users who clear storage mid-session and the fallback when JavaScript or storage access is blocked. - `auth-storage.ts` exposes `hasStoredAuthTokens` and the localStorage keys. - `public-paths.ts` is the single source of truth for unauthenticated routes, derived from `app/routes/` (`_auth.*`, `_public.*`, `api.*`, `.well-known/`). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The `/home-v1` route and `app/features/homepage-v1/` directory were both
introduced by this PR — they never existed on master. The previous `/home`
was a small Coming Soon placeholder, not the v1 marketing page. Keeping
both versions in this PR was misleading ("archive of the previous
version") and noisy for review.
Removes:
- app/features/homepage-v1/ (14 files, 806 LOC)
- app/routes/_public.home-v1.tsx (the route exposing it)
- '/home-v1' entry from PUBLIC_PATH_PREFIXES (now an orphan)
The `/home-v1` entry in `app/features/auth/public-paths.ts` (added in
42727e3) was strictly tied to the v1 route's existence; dropping it is
a mechanical consequence of the v1 removal, not a behavior change.
…tags Both agi.cash and agi.cash/home should preview identically. Removing the per-route meta export in _public.home.tsx makes /home inherit root.tsx's description instead of diverging.
The pixelated wipe between specimen cards left a one-frame gap between when the overlay finished and when the underlying card settled to its resting state, producing a visible flash. The previous img and the wipe unmount were committed in the same React batch, but the browser needed a paint cycle to upload the new img bitmap, so the previous card's pixels were briefly visible after the SVG overlay was removed. Split the timer: swap imgIdx at WIPE_FULLY_OPAQUE (600ms, when every cell has reached opacity 1 and the wipe fully covers the underlying img) and unmount the wipe 80ms later. The new img is painted beneath the still-mounted overlay, so the unmount reveals the next card with no flash of the previous one.
Replaces the entry.client.tsx pre-hydration redirect workaround with a fix at the SSR layer. The flash on prerendered routes is caused by React's streaming SSR outlining Suspense boundaries when the rendered HTML exceeds progressiveChunkSize (12.8 KB by default), which puts the fallback in the shell and swaps the content in via an inline script. That's a streaming optimization with no upside for prerender (the response is buffered to disk anyway), so we disable it for prerender requests by setting progressiveChunkSize to Infinity. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vercel doesn't serve react-router's prerendered HTML as static files when ssr:true (remix-run/react-router#14281), so /home and friends fall through to the SSR function on every request — losing the edge-cache + cold-start + cost benefits of prerender, and re-triggering the Suspense outlining flash since runtime SSR doesn't get the !user-agent prerender override. This commit hardcodes the rewrites as a quick validation that explicitly mapping each prerendered path to its build output bypasses the upstream bug. If verification confirms it works, the path list will be moved to a shared module in a follow-up so react-router.config.ts and vercel.json have a single source of truth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit's rewrites are being ignored — /home still hits the SSR function on the deploy preview (x-vercel-id has a function region). Vercel's react-router framework preset appears to intercept user rewrites for routes it has registered. cleanUrls operates at a different layer in Vercel's routing pipeline (auto-resolves bare paths to .html or directory-index static files), which may bypass the interception. Keeping the explicit rewrites alongside as belt-and-suspenders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…erception Rewrites and cleanUrls both confirmed not effective — /home still hits the SSR function on the deploy preview, framework preset overrides them. Redirects are processed earlier in Vercel's routing pipeline; if they fire, that proves the framework preset intercepts at the rewrite layer but not the redirect layer (and that the static file serves correctly when reached). URL bar will change to /home/index.html — purely diagnostic, not the final shape of the fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… stripping cleanUrls auto-redirects /home/index.html -> /home, which combined with our test redirect /home -> /home/index.html produced an infinite loop. Drop cleanUrls to verify the redirect approach works end-to-end (URL will land on /home/index.html serving the static file from edge). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit only disabled outlining for build-time prerender (no user-agent on the request). Vercel currently doesn't serve our prerendered HTML as static files when ssr:true (remix-run/react-router#14281), so runtime requests for /home and friends fall through to the SSR function — which has the default progressiveChunkSize, outlines the user-level Suspense boundary, and produces the same fallback flash that the build-time fix solved. Extract the prerender path list into app/prerender-paths.ts so react-router.config.ts and entry.server.tsx share a single source of truth, and check the request path at SSR time. Real prerender (no UA) and runtime SSR of prerender-eligible routes both get progressiveChunkSize: Infinity; everything else keeps streaming. Revert the vercel.json rewrite/redirect/cleanUrls experiments — none worked: the framework preset overrides user rewrites, and redirects break SPA hydration because the static file is prerendered for /home, not /home/index.html. Once the upstream Vercel adapter bug is fixed, the path-list check can be removed and the no-UA check alone will cover prerender invocations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pragmatic conversion of ~1545 lines of custom CSS in app/features/homepage/styles.css to Tailwind utilities applied inline in JSX. Keyframes and a few complex selectors remain in styles.css and the global tailwind.css. Goal: visual fidelity preserved, idiomatic Tailwind throughout. Preview-only branch — not for merge into #1050. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The custom .marketing .font-mono override was dropped during the Tailwind conversion, causing every font-mono utility to fall back to ui-monospace instead of the brand Kode Mono face. Reinstating the 3-line scoped override. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…d v4 The conversion used the v3 data-type-hint syntax `font-[family:var(...)]` which Tailwind v4 silently drops (the `family:` hint was removed in v4). Result: Kode Mono, Cabinet Grotesk and Teko were all falling back to default font stacks in the device-mockup elements. Replace with arbitrary-property syntax `[font-family:var(...)]` which both versions parse correctly. Also swap `[font-feature-settings:'tnum']` for the first-class `tabular-nums` utility — equivalent visual effect without the inner-single-quote that confused biome's class sorter. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
~31ms spacing instead of ~18ms so adjacent cells don't share a frame at 60fps
…iminate iOS render snap Was rendering incoming via SVG pattern then swapping to <img>; subpixel mismatch caused a right-shift on iOS Safari. Now incoming is always rendered as a plain <img>; SVG dissolves outgoing away on top. End-of-transition is just an SVG unmount over a card that was never re-rendered.
Wrap each section's h2 heading text in an <a href="#name"> so the heading title is a real anchor tag — clicking it updates the URL hash and the page scrolls to the heading. Move the id from the outer Section wrapper onto the anchor itself for 5 of 6 sections, so URL fragments land on the heading text (not the section's top padding). Wallet keeps its id on the outer Section because it has two responsive h2s (mobile + desktop, visually exclusive) and can't host a single unique id on either h2 alone — both h2s are still clickable anchors that link to #wallet. Add scroll-margin-top: 80px so the heading sits below the sticky marketing nav after navigation, instead of being hidden under it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- tailwind.css: remove 11 unused --animate-* utility bindings and 3 dead keyframes (pixel-cell, hero-pixel-cell, spec-enter). The remaining marketing keyframes are referenced directly via animation: in features/homepage/styles.css and don't need utility bindings. - styles.css: drop the .pixel-wipe rect block — no consumer in TSX. - hero-section: collapse imgIdx into activeIdx. Both were batched to the same value in the same render and could never diverge. activeIdx now drives both the bottom <img> and the meta labels. - hero-section: rel="noreferrer" → rel="noopener noreferrer" on the merchant link. - merchants-section: replace three near-identical logo wrappers with a supportedSystems array + inline map. Drop stale "inline SVG" comment. - footer-section: remove redundant aria-label="Agicash" on the wordmark; the visible text already conveys it.
…l-dissolve Relocate each section's hash anchor target from the heading <a> to its SectionLabel (now optionally an <a id href>). Narrows the scroll-margin-top selector to .marketing a[id] to match. Hero pixel-dissolve: render outgoing card as a sibling <img> masked by an SVG <mask> instead of painting it via an SVG <pattern>, so both layers share the same paint pipeline and anti-aliasing — eliminates a 1px desktop ghost shift at DPR=1 (SVG <image> and HTML <img> paint fractional-pixel boxes with different AA). Add an inset 1px dark hairline so per-image edge color variation (e.g. PubKey's near-black bottom-right vs Epicurean's blue-gray) stops reading as a border shift mid-dissolve.
Hero pixel-dissolve regressed on iOS Safari with the previous CSS-mask approach (mask-image: url(#fragment) on an HTML <img>) — WebKit snapshots the mask and skips the per-frame opacity animation, so the outgoing card no longer dissolves. Refactor both cards into a single <svg> with two <image> children; outgoing carries an SVG-native mask="url(#…)" pointing at an inline <mask> whose rects step opacity 1 → 0. One paint pipeline for both layers (no desktop AA shift) and SVG-native masks have been reliable in WebKit for years. Inset edge shadows move from the <img> to a sibling overlay div so they still paint over the SVG. Move section hash anchors back onto the outer <section id="…"> wrapper so #buy / #send / etc. land at the top of each section rather than mid- content on the SectionLabel itself (Bob's report: hash navs were cutting off the section's top under the sticky nav). SectionLabel stays a real <a href="#…"> so clicks still update the URL hash, but no longer carries the id. scroll-margin-top selector narrows back to .marketing section[id].
Captures the brainstorming output: layered hexagonal architecture with sans-IO core, Rust-owned cache, multi-platform via UniFFI/WASM, CLI as v1 deliverable. Covers crate layout, state + concurrency, Supabase RPC boundary, Open Secret auth, Cashu/Spark providers, WASM boundary, CLI surface, and known costs.
Expands the spec with: - Distributed task-processing lock via take_lead (Section 4) - Multi-device realities for Spark/Breez and Cashu (Section 7) - Cache reconciliation across devices (Section 8) - Concurrency retry strategy + idempotency guarantees (Section 11) - TDD-friendly implementation slicing (Section 16) — scaffold → auth → per-feature slices, each with explicit test bar
- WalletClient builder no longer takes a separate .breez(...) handle; SparkProvider::connect() owns the Breez session internally. - SparkProvider trait drops the breez() and account_handle() leaks; exposes wallet_for_account() symmetric with CashuProvider.
PubKey (116KB webp) is the first hero card rendered. Without a preload the request was queued behind the marketing JS bundle parse, so the hero card slot stayed blank for ~hundreds of ms on a cold load. The other 5 cards are decoded post-mount by HeroSection's decodedImagesRef useEffect, so a single preload is enough for first paint.
Internal planning doc that landed on the homepage branch by mistake. Not relevant to this branch's scope; removing so it doesn't merge into master through the homepage PR.
The marketing nav's height differs by breakpoint (65px mobile, 71px desktop, both measured), so the previous flat 80px scroll-margin-top overshot at both viewports — leaving a 15px gap on mobile and a 9px gap on desktop between the nav's border-b and each section's border-t. Replace the single rule with a responsive value keyed to Tailwind's md breakpoint (768px), the same breakpoint the nav itself steps at. Use nav-height minus 1px so the section's 1px border-t overlays the nav's 1px border-b and they read as a single continuous line.
Summary
/homewith a real homepage focused on Bitcoin gift cards (five sections + consumer / merchant CTAs + AGICASH wordmark footer)./→/homefor logged-out users still wires up the Get Started CTA to/signup.What's in the homepage
> for_users(consumer Join Beta) and> for_merchants(mailto:merchants@agi.cash) with Square / BTCPay Server / Shopify logo stripStack
app/features/homepage/; no wallet code touchedapp/assets/Test plan
/home(logged out) and verify v2 renders/login, Sign up →/signup, Get Started in hero →/signupPay→ loading auto-advances → clickConfirm and pay→ clickDone→ clickOK→ loops> replayto restart, counter ticks 0.000s → 0.083sprefers-reduced-motion: reducedisables hero stagger, pixel wipes, transit, scan, buy transitionsNotes for review
--no-verifybecause master's pre-commit typecheck is currently failing on a pre-existing Supabase typing issue inapp/routes/api.lnurlp.verify.$encryptedQuoteData.ts(unrelated to this PR). CI will run all checks against this branch.merchants@agi.cashmailto link assumes that address forwards to the team — please verify or swap it before merge.🤖 Generated with Claude Code