feat(landing-v2): editorial scroll-narrative landing with GSAP + Lenis#3
Draft
hosein-ul wants to merge 18 commits into
Draft
feat(landing-v2): editorial scroll-narrative landing with GSAP + Lenis#3hosein-ul wants to merge 18 commits into
hosein-ul wants to merge 18 commits into
Conversation
- Replace all Etherscan links with Blockscout (eth-sepolia.blockscout.com / eth.blockscout.com) — Blockscout supports Zama FHE protocol decoding - Replace hardcoded zamavault.xyz in docs/developers/API with NEXT_PUBLIC_APP_URL env var (falls back to placeholder until deployed) - Add NEXT_PUBLIC_APP_URL to .env.example with documentation - chains.ts: update explorerUrl for both Sepolia and Mainnet
…permit bug TanStack Query's refetch() bypasses the enabled flag unconditionally. After a successful shield or unshield, calling refetchWrapperBalance() was silently triggering useConfidentialBalance even though decryptRequested=false, causing the EIP-712 permit wallet prompt to auto-fire without user consent (and retry up to 3x before surfacing the rejection error). Fix: remove refetchWrapperBalance() from both success paths in handleAction. The confidential balance is only refreshed when the user explicitly clicks the "Decrypt" button (which sets decryptRequested=true before calling refetch).
After a successful wrap/unwrap, setDecryptRequested(false) prevents TanStack Query's refetchOnWindowFocus from auto-firing the EIP-712 permit (which caused the wallet to prompt up to 3 times for a permit the user had not explicitly requested). The user must click Decrypt again to see the refreshed confidential balance after a transaction.
Phase 3.1 — /analytics page: - TVL per token via balanceOf on underlying ERC-20 held by wrapper - Shield/Unshield event counts from Transfer logs (last 5000 blocks) - Recent activity feed sorted by block number - 4 stat cards: active pairs, shields, unshields, unique shielders - Refresh button, Blockscout links, loading skeletons Phase 3.2 — Portfolio activity feed: - WalletActivityFeed component at bottom of portfolio - Shows recent shield/unshield events for connected wallet (last 10000 blocks) - Links to Analytics page Phase 4.4 — Mobile responsive CSS: - Header nav hidden on mobile, container padding reduced - Grid-2 collapses to 1 column at 768px - Swap panel, modal, typography scale for 360px–480px - Logo text hidden at 480px to save space - Network switcher compact on small screens
- formatTVLCompact(): 22976602.1114 → 23.00M (fixes display overflow) - estimateTimeAgo(): block-based time estimate without extra RPC calls (latestBlock - eventBlock) × 12s → '2m ago', '3h ago', etc. - Activity: show up to 25 events (max 8 per token), add timestamp to each row - Add amount localeString formatting (commas for thousands) - New: Shield vs Unshield ratio bar card - New: Most Active Token card (by tx count) - New: shieldVolume / unshieldVolume tracking (period volume in TVL bar) - insights-grid 2-col responsive layout
- Shield: skip Approve step in UI when allowance is already sufficient (SDK auto-skips it; now UI matches by starting at step 3) - Unshield: add onFinalizing callback — shows toast when Zama Gateway is generating the decryption proof (15-40s wait phase) - Unshield: add onFinalizeSubmitted callback — shows finalization tx - Step indicator: Approve step only shown when needsApproval is true - Step indicator: Unshield now shows 3 steps (Unwrap → Finalize → Done) instead of 2, matching the actual 2-phase protocol flow
Root cause: .badge CSS class had text-transform:uppercase which turned "cZAMA" into "CZAMA", "cUSDC" into "CUSDC" everywhere badges were used. - Remove text-transform:uppercase from .badge class - Token selector in wrap page: shows "cBRON", "cZAMA" etc when in unwrap mode (From = Confidential), plain "BRON", "ZAMA" in wrap mode - Verified all symbol conventions: - Public ERC-20: ZAMA, USDC, WETH, BRON, etc. - Confidential ERC-7984: cZAMA, cUSDC, cWETH, cBRON, etc. - c is always lowercase per Zama convention
analytics: - Fix TVL sort: was comparing raw bigints (wrong — ZAMA 18-dec raw >> USDC 6-dec raw for equal human values). Now sorts by tvlHuman (float normalized by decimals). USDC 22.89M now correctly ranks above tGBP 3.86M. - Fix bar width: same normalization applied to bar percentage calculation. - Change period: 5000 blocks (~17h) → 7200 blocks (24h exactly at 12s/block). - Update all "~17h" text to "~24h". registry page: - Shorten all tooltip text significantly (1-2 lines max). - Fix tooltip triggers: Mock badge and Confidential badge now have a separate ? icon (Tooltip standalone), not the badge itself as trigger.
Hero section added above registry table on home page: - CrystalLattice: R3F 3D scene with morphing icosahedron wireframe (simplex noise displacement), gold activation waves, instanced diamond particles drifting outward, shield prism with MeshPhysicalMaterial (transmission 0.9, IOR 2.2, rainbow refraction) - HeroHeadline: "EVERY BIT. ENCRYPTED." with per-character Framer Motion spring animation (scatter → assemble), prismatic CSS shimmer on second line, golden period, BlurIn subheadline - HeroCTA: chamfered clip-path buttons with prismatic light sweep on hover, center-outward fill on secondary button - Stats row: 8 Token Pairs / FHE / ERC-7984 - Scroll transition: content parallax + opacity fade, lattice shatter (scale explosion), diamond particle burst - Mobile responsive: badges hidden, stacked CTAs, smaller headline - prefers-reduced-motion: all animations disabled, static render - Code-split: CrystalLattice lazy-loaded via next/dynamic (ssr: false) - Theme reactive: reads --accent from CSS custom properties via MutationObserver on data-design-theme attribute changes - New deps: three, @react-three/fiber, @react-three/drei, framer-motion, d3-delaunay, simplex-noise
Separate from dashboard — no app Header/Footer via route group layout. Design: Zama brand (gold #FFD208 primary, clean dark #0a0a0a bg). Sections: - Sticky nav with Launch App CTA - Hero: headline with gold accent + live portfolio card visual - Stats bar: 8 pairs / 2 networks / FHE / ERC-7984 - Features: 3 cards (Registry / Shield / Decrypt) - How it works: 3 numbered steps with code hints - Token grid: all 7 pairs with color dots - CTA section with gold line border - Footer with links
Architecture
- Move the application from / to a /app/* subtree so the marketing
landing can live independently at /. Page renames preserve history.
- Two design systems, hermetically separated:
* src/app/layout.tsx is now CSS-free at the root (only fonts +
theme bootstrap).
* src/app/page.tsx imports landing.css (Tailwind v4 + @theme tokens).
* src/app/app/layout.tsx imports globals.css and wraps ClientLayout.
- Tailwind v4 (@tailwindcss/postcss) + Magic UI primitives installed
under src/components/magic/.
- New landing sections under src/components/landing/ (hero, features,
trust rail, stats, how-it-works, FHE explainer, final CTA, footer).
- Fonts: Fraunces (display) + Plus Jakarta Sans (body) via next/font.
- All internal links rewritten to the /app/* prefix.
- src/lib/utils.ts cn() upgraded to clsx + twMerge for shadcn compat.
Wrap page polish
- Primary Shield/Unshield button is now always in the action slot;
tx-info card and gateway-delay warning render BELOW it (never replace).
- Card max-width widened to 580px so all sections stay visible during
the transaction state.
- Pulse-gold border halo while txStep is in flight (1..4) via the new
.swap-card-pending rule + @Keyframes in globals.css.
- Approval flow: pass approvalStrategy "skip" when existing allowance
is already sufficient, "exact" when not. Avoids the SDK's default
approve(0) + approve(amount) double-prompt on every shield.
Landing polish
- Features grid is now a clean 3×2 with no row-span / no orphan tile.
MagicCard orb softened to 0.45 opacity / 260 size.
- Hero text reveal: three single TextAnimate lines at default Magic UI
timing (by="word"), wrapped in a motion.div opacity safety fade so
content reads instantly even on slow devices.
- BFCache recovery: new <BFCacheRecovery /> client component reloads
the page on `pageshow` with event.persisted === true. Fixes the
invisible-content bug after returning via browser back from an
external link.
Analytics + Learn + misc.
- Analytics: rename TVL → TVS (Total Value Shielded), labels switched
to "last 24h", block lookback now calculated from 00:00 UTC today.
- Learn page: confetti on tutorial completion (only after the explicit
"Mark as Done" on step 5), Go-To buttons resized down so they don't
outweigh nav buttons.
The previous README was significantly out of date: - claimed a single-page app at / - named four design themes that no longer exist (Nordic Clean / Cyber / Nebula / Emerald) - omitted every page added since (Landing, Analytics, Learn, Dev-Tools, Docs, REST API endpoint) - mis-described the unshield flow as a single transaction The new README covers: - accurate /app/* routes table - the two-design-system architecture (landing.css vs globals.css) - ERC-1363 single-tx shield path vs approve+wrap two-tx path, citing the Zama docs - two-phase unshield with Gateway finalize + resume banner - /api/registry REST endpoint schema and example response - decimal-scaling note (wrappers always 6 decimals due to euint64) - accurate tech stack pulled from package.json - project conventions (no auto-fire permits, strict TS, two-design systems never mix)
A brand-new landing at / on a dedicated branch. v1 design is left fully intact on disk (src/components/landing/, src/app/landing.css) so the previous design is one-branch-switch away. Identity - Cream base (#FAF7F1), gold accent (#FFD208), aubergine (#3B1F2B) for the single dark beat. Editorial — no all-dark theme. - Fraunces (display, serif) + Plus Jakarta Sans (body) — already loaded via next/font from the root layout. - All tokens in a new src/app/landing-v2.css, isolated from landing.css and globals.css via Next.js route-subtree CSS scoping. Scroll plumbing - Lenis smooth-scroll provider bridges into GSAP ScrollTrigger so pinned timelines stay aligned with the lerp'd scroll. - Scroll-progress rail pinned to the right viewport edge: a 2 px gold line scrubbed by document scroll progress, plus a label showing the current section name (driven by IntersectionObserver). - Noise background — Canvas 2D + simplex-noise — fixed behind everything, hue and dot density shift based on scroll progress through the page narrative (cream → gold-warm → aubergine drift). Sections (top to bottom) - Aperture: gold dot blooms into halo, two-line serif headline. - Manifesto: pinned 130 % vh, paragraph word-reveals on scrub, then a gold underline draws beneath the closing phrase. - Problem ledger: mock public transfers + what an observer can derive. - Solution encrypt: pinned 150 % vh, same ledger SCRAMBLES character-by- character into ciphertext via GSAP onUpdate; a gold "euint64 · on-chain" pill and explanation fade in at the end. - Token constellation: real CMC logos for the 7 confidential pairs. - Capabilities: 3 × 2 editorial grid, six tools, one consistent design. - Numbers: four count-up figures with custom easeOutQuart. - Flow: vertical timeline whose gold rail fills by scroll position (native IntersectionObserver, no GSAP needed for this one). - For Devs: three syntax-stripped code blocks (React / curl / viem). - Final beat: aubergine, big serif "Your tokens. Your privacy. On-chain." - Footer: editorial wide-gutter, fine type. Tech - New deps: gsap ^3.15, lenis ^1.3 - Reuses existing: motion (framer-motion), simplex-noise, lucide-react, react-icons, next/font (Fraunces + Plus Jakarta Sans). - Respects prefers-reduced-motion globally — Lenis no-ops, GSAP timelines early-return, noise canvas freezes one static frame. - BFCacheRecovery reused from v1.
…hield on wrap Analytics - Rename getBlocksSinceMidnightUTC() → getBlocksSinceUtcMidnight() for clarity. - Enforce a minimum of 7200 blocks (~24h) in the UTC-midnight lookback so the page always has meaningful TVS stats even at 00:01 UTC when only a handful of blocks have elapsed since midnight. Before the minimum, a page load at 00:01 UTC returned ~5 blocks, making every count appear as 0. Portfolio / My Recent Activity - Changed fromBlock from `latestBlock - 10000n` (~34h) to 0n so the wallet activity feed shows the user's complete on-chain history. - Added a "block range too large" error fallback that retries with `latestBlock - 500000n` (~69 days) for providers with strict range limits. - Bumped display cap from 20 to 100 events. - Updated empty-state copy: removed the stale "~34 hours" figure. Wrap page — Pending Unshield - PendingUnshieldBanner is now rendered for ALL registered wrappers (not only the currently selected token). Each banner self-hides when there is no pending unshield for that token, so there is zero visual noise when everything is clean. Previously a user who returned mid-unshield without first selecting the right token in the dropdown would never see the banner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Brand-new landing at
/on a dedicated branch. The previous landing design is left fully intact on disk (src/components/landing/,src/app/landing.css) so it's reachable with a singlegit checkout.Marked draft — depends on PR #2 (
feat/dynamic-registry-finding-1) being merged first.Identity
#FAF7F1), gold accent (#FFD208), aubergine (#3B1F2B) for the single dark beat. No all-dark theme.next/fontfrom the root layout.src/app/landing-v2.css, isolated fromlanding.cssandglobals.cssvia Next.js route-subtree CSS scoping.Scroll plumbing
simplex-noise, fixed behind everything. Hue and dot density shift based on scroll progress (cream → gold-warm → aubergine drift).Sections (top to bottom)
onUpdate; gold "euint64 · on-chain" pill and explanation resolve at the endeaseOutQuartDependencies
gsap@^3.15(ScrollTrigger is now MIT-licensed),lenis@^1.3motion(framer-motion),simplex-noise,lucide-react,react-icons,next/fontAccessibility
prefers-reduced-motionglobally — Lenis no-ops, GSAP timelines early-return, noise canvas freezes a single static frame.<Link>or<a>— keyboard navigable.motion.divopacity safety fades.Test plan
npx tsc --noEmit— cleannpx next build— all 11 routes prerendered cleanlyaperture, manifesto, problem, solution, tokens, capabilities, numbers, flow, devs, final)<html>(class observed)rgb(59, 31, 43)— not blackprefers-reduced-motion: reduce— content should be readable without animations