From 012d4904b0ad194c2d695e4973924a3eb0c3f179 Mon Sep 17 00:00:00 2001 From: 0xkkonrad Date: Fri, 14 Aug 2026 09:27:52 +0000 Subject: [PATCH 01/10] =?UTF-8?q?feat(landing):=20homepage=20rebuild=20?= =?UTF-8?q?=E2=80=94=20main-based=20port=20of=20the=20LP=20refresh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the whole LP change set onto main so it can ship to production. dev is 75 commits ahead of main, so #2690 cannot simply retarget; this is the same diff rebuilt on main's tree. Of the 51 files in the change set, 35 are byte-identical between main and dev and carried over whole. The other 16 also move on dev, so each was three-way merged with dev as the base — main's version plus the LP delta and nothing else. That keeps dev's unrelated work out: main's catalogs gain the seven ProblemFold keys, lose landingPayLocalSettles, and take the reworded support and footer strings, but none of dev's other new keys come along. src/content stays on main's pointer. Production content moves on its own pipeline and must not be dragged backwards or forwards by a code PR. --- redirects.json | 5 + scripts/generate-valid-links.ts | 2 +- scripts/ping-indexnow.ts | 2 +- scripts/verify-content.ts | 2 +- src/app/lp/card/CardLandingPage.tsx | 867 ------------------ src/app/lp/card/page.tsx | 25 - src/app/robots.ts | 1 - src/app/shhhhh/ShhhhhLandingPage.tsx | 23 +- src/app/shhhhh/page.tsx | 2 +- src/app/sitemap.ts | 6 +- src/assets/icons/pix-logo.svg | 2 +- src/assets/illustrations/flag-ar.svg | 35 + src/assets/illustrations/flag-br.svg | 42 + src/assets/illustrations/stack-coin.svg | 8 + src/assets/logos/index.ts | 2 - src/components/Card/doorTally.utils.ts | 9 +- .../Card/share-asset/PixelatedCardFace.tsx | 7 +- src/components/Global/FAQs/index.tsx | 17 +- .../Global/MarqueeWrapper/index.tsx | 25 +- .../Global/MarqueeWrapper/marquee.types.ts | 10 + src/components/LandingPage/CardPioneers.tsx | 115 --- .../LandingPage/LandingPageClient.tsx | 112 ++- .../LandingPage/LandingPageContent.tsx | 11 +- src/components/LandingPage/Manteca.tsx | 79 +- src/components/LandingPage/PartnerLockup.tsx | 56 ++ src/components/LandingPage/PioneerCard3D.tsx | 158 ---- src/components/LandingPage/ProblemFold.tsx | 77 ++ src/components/LandingPage/RegulatedRails.tsx | 59 +- .../LandingPage/ScarcityCounter.tsx | 40 + src/components/LandingPage/ShhhhhFold.tsx | 76 ++ .../__tests__/landingLinks.utils.test.ts | 61 ++ src/components/LandingPage/faq.tsx | 4 +- src/components/LandingPage/index.ts | 1 - src/components/LandingPage/landing.types.ts | 15 + .../LandingPage/landingLinks.utils.ts | 49 + src/components/LandingPage/landingStrings.ts | 13 + src/components/LandingPage/marquee.tsx | 4 +- .../LandingPage/securityBuiltIn.tsx | 16 +- src/components/LandingPage/yourMoney.tsx | 22 +- src/config/underMaintenance.config.ts | 11 +- src/i18n/app/__tests__/shhhhh-catalog.test.ts | 2 +- src/i18n/app/messages/en.json | 4 +- src/i18n/app/messages/es-419.json | 4 +- src/i18n/app/messages/es-AR.json | 2 +- src/i18n/app/messages/pt-BR.json | 4 +- src/i18n/en.json | 12 +- src/i18n/es-419.json | 10 +- src/i18n/es-ar.json | 10 +- src/i18n/pt-br.json | 10 +- src/i18n/types.ts | 10 +- 50 files changed, 828 insertions(+), 1311 deletions(-) delete mode 100644 src/app/lp/card/CardLandingPage.tsx delete mode 100644 src/app/lp/card/page.tsx create mode 100644 src/assets/illustrations/flag-ar.svg create mode 100644 src/assets/illustrations/flag-br.svg create mode 100644 src/assets/illustrations/stack-coin.svg create mode 100644 src/components/Global/MarqueeWrapper/marquee.types.ts delete mode 100644 src/components/LandingPage/CardPioneers.tsx create mode 100644 src/components/LandingPage/PartnerLockup.tsx delete mode 100644 src/components/LandingPage/PioneerCard3D.tsx create mode 100644 src/components/LandingPage/ProblemFold.tsx create mode 100644 src/components/LandingPage/ScarcityCounter.tsx create mode 100644 src/components/LandingPage/ShhhhhFold.tsx create mode 100644 src/components/LandingPage/__tests__/landingLinks.utils.test.ts create mode 100644 src/components/LandingPage/landingLinks.utils.ts diff --git a/redirects.json b/redirects.json index f29571fec6..5369087048 100644 --- a/redirects.json +++ b/redirects.json @@ -141,6 +141,11 @@ "destination": "/", "permanent": true }, + { + "source": "/lp/card", + "destination": "/shhhhh", + "permanent": true + }, { "source": "/sh", "destination": "/card", diff --git a/scripts/generate-valid-links.ts b/scripts/generate-valid-links.ts index 82911450c0..64bd4b1c70 100644 --- a/scripts/generate-valid-links.ts +++ b/scripts/generate-valid-links.ts @@ -59,7 +59,7 @@ function main() { // Static pages lines.push('## Static Pages') lines.push('') - for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms', '/lp/card']) { + for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms']) { lines.push(`- \`${p}\``) } lines.push('') diff --git a/scripts/ping-indexnow.ts b/scripts/ping-indexnow.ts index f0d0e0c1bd..c9ba84493d 100644 --- a/scripts/ping-indexnow.ts +++ b/scripts/ping-indexnow.ts @@ -27,7 +27,7 @@ async function getAllPaths(): Promise { const { SUPPORTED_LOCALES } = await import('../src/i18n/types') const { listContentSlugs } = await import('../src/lib/content') - const paths: string[] = ['/', '/lp/card', '/careers', '/exchange', '/privacy', '/terms'] + const paths: string[] = ['/', '/shhhhh', '/careers', '/exchange', '/privacy', '/terms'] for (const locale of SUPPORTED_LOCALES) { for (const country of Object.keys(COUNTRIES_SEO)) { diff --git a/scripts/verify-content.ts b/scripts/verify-content.ts index c45997ffce..79418ccc9e 100644 --- a/scripts/verify-content.ts +++ b/scripts/verify-content.ts @@ -157,7 +157,7 @@ function discoverRoutes(): Set { const routes = new Set() // Static pages - for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms', '/lp/card']) { + for (const p of ['/', '/careers', '/exchange', '/privacy', '/terms']) { routes.add(p) } diff --git a/src/app/lp/card/CardLandingPage.tsx b/src/app/lp/card/CardLandingPage.tsx deleted file mode 100644 index 6444eefc27..0000000000 --- a/src/app/lp/card/CardLandingPage.tsx +++ /dev/null @@ -1,867 +0,0 @@ -'use client' -import { motion } from 'framer-motion' -import Image from 'next/image' -import { Button } from '@/components/0_Bruddle/Button' -import { getFlagUrl } from '@/constants/countryCurrencyMapping' -import { FAQsPanel } from '@/components/Global/FAQs' -import PioneerCard3D from '@/components/LandingPage/PioneerCard3D' -import { Marquee } from '@/components/LandingPage' -import { useAuth } from '@/context/authContext' -import { useRouter } from 'next/navigation' -import Star from '@/assets/illustrations/star.svg' -import HandThumbsUp from '@/assets/illustrations/hand-thumbs-up.svg' -import { useEffect } from 'react' -import underMaintenanceConfig from '@/config/underMaintenance.config' - -const faqQuestions = [ - { - id: '0', - question: 'What is Card Pioneers?', - answer: 'Card Pioneers is the early-access program for the Peanut Card. Reserve a spot, get priority rollout access, and unlock Pioneer perks like $5 for every friend you refer.', - }, - { - id: '1', - question: 'How does it work?', - answer: '1. Reserve your spot by adding $10 in starter card balance. 2. Share your Peanut invite link. 3. When someone joins Card Pioneers through your invite, you earn $5 instantly, plus rewards every time they spend - forever.', - }, - { - id: '2', - question: 'Is the $10 refundable?', - answer: "Your $10 becomes starter balance when your card launches. If you're found not eligible at launch (for example: your region isn't supported, or you can't complete required verification), you can request a refund.", - }, - { - id: '3', - question: 'Where is the card available?', - answer: "We're rolling out by region in stages: US, Latin America, and Africa. You'll see eligibility details during signup.", - }, - { - id: '4', - question: "Do I earn from my invites' invites too?", - answer: 'Yes! You earn a smaller part for your entire invite tree. So if you invite someone who becomes a power-referrer, you earn from everyone they bring in too.', - }, -] - -const CardLandingPage = () => { - const { user } = useAuth() - const router = useRouter() - // feature flag: redirect to landing if card pioneers is disabled - useEffect(() => { - if (underMaintenanceConfig.disableCardPioneers) { - router.replace('/') - } - }, [router]) - - if (underMaintenanceConfig.disableCardPioneers) { - return null - } - - const handleCTA = () => { - if (user) { - router.push('/card') - } else { - router.push('/setup?redirect_uri=/card') - } - } - - // Marquee copy from CARD_coremessaging.md - const marqueeProps = { - visible: true, - message: ['EARLY = EARN', 'BUILD YOUR TREE', 'ONE LINK', 'LIFETIME UPSIDE', '$5 PER INVITE', 'EARN FOREVER'], - } - - return ( - <> - {/* Hero Section - Yellow with card */} -
- - -
-
- - YOUR DOLLARS. -
- EVERYWHERE. -
- - - Pay with the peanut card. Earn with every purchase, yours or your friends. -
-
- Non-custodial. Best rates. No hidden fees. -
- - - - - - - -

- $10 starter balance = your spot secured -

-
-
-
-
- - - - {/* How it works - Cream */} -
- - -
- - HOW IT WORKS - - -
- - - -
-
-
- - - - {/* Earn Forever - Cream Background */} -
- -
-
- {/* Visual - Simplified Invite Visual */} - -
- {/* - LAYOUT - Calculated with Python trigonometry - Container: 340x380 - - L0 (YOU): center (170, 190), 80x80px - L1 nodes: 48x48px, 120px from YOU - - Top: center (170, 70) - outward angle -90° - - Bottom-left: center (70, 310) - outward angle 129.8° - - Bottom-right: center (270, 310) - outward angle 50.2° - - L2 nodes: 32x32px, 55px from parent L1 center, fanning at -45°, 0°, +45° from outward direction - Top L1 (170,70): (131,31), (170,15), (209,31) - Bottom-left L1 (70,310): (75,365), (35,352), (15,315) - Bottom-right L1 (270,310): (325,315), (305,352), (265,365) - */} - - {/* Connection lines */} - - {/* L0 to L1 edges */} - - - - - {/* Top L1 (170,70) to L2 */} - - - - - {/* Bottom-left L1 (70,310) to L2 */} - - - - - {/* Bottom-right L1 (270,310) to L2 */} - - - - - - {/* L0: YOU node - center (170,190), top-left (130,150) */} -
- YOU -
- - {/* +$5 BADGES - at visual midpoint between node edges - Top edge: YOU bottom (y=150) to L1 top (y=94) -> visual mid = (150+94)/2 = 122, badge top = 114 - Bottom edges: at midpoint of line between centers - */} - - +$5 - - - +$5 - - - +$5 - - - {/* L1: Top primary - center (170,70), top-left (146,46) */} -
- -
- - {/* L1: Bottom-left primary - center (70,310), top-left (46,286) */} -
- -
- - {/* L1: Bottom-right primary - center (270,310), top-left (246,286) */} -
- -
- - {/* L2 NODES - positioned directly at calculated centers - Each node is 32x32, so top-left = center - 16 - Top L1 (170,70): L2 at (131,31), (170,15), (209,31) - Bottom-left L1 (70,310): L2 at (75,365), (35,352), (15,315) - Bottom-right L1 (270,310): L2 at (325,315), (305,352), (265,365) - */} - - {/* Top L1's children - labels 2px gap from node edge */} -
- -
- - +% - - -
- -
- - +% - - -
- -
- - +% - - - {/* Bottom-left L1's children */} -
- -
- - +% - - -
- -
- - +% - - -
- -
- - +% - - - {/* Bottom-right L1's children */} -
- -
- - +% - - -
- -
- - +% - - -
- -
- - +% - -
-
- - {/* Copy */} - -

- INVITE ONCE. -
- EARN FOREVER. -

- -
- - - -
- - -
-
-
-
- - - - {/* Coverage - Yellow */} -
- - -
- - ROLLING OUT -
- GLOBALLY -
- -

- Starting with US, Latin America, and Africa -

- - - {/* Individual country flags */} - {[ - { name: 'United States', code: 'us' }, - { name: 'Brazil', code: 'br' }, - { name: 'Argentina', code: 'ar' }, - { name: 'Mexico', code: 'mx' }, - { name: 'Nigeria', code: 'ng' }, - { name: 'Kenya', code: 'ke' }, - { name: 'South Africa', code: 'za' }, - ].map((country, i) => ( - - {`${country.name} - {country.name} - - ))} - - {/* Region pills without flags */} - - Latin America - - - Africa - - - + more - - -
-
- - - - {/* FAQ - Cream */} -
- - -
- - - More questions? Visit our{' '} - - support page - - -
-
- - - - {/* Final CTA - Secondary Yellow */} -
- - -
- - - Early access is open - - - - READY TO -
- JOIN? -
- - $10 reserves your spot. And for every friend you invite, earn forever. - - - - -
-
- - - - ) -} - -// Floating stars component -const FloatingStars = () => { - const starConfigs = [ - { className: 'absolute left-12 top-10', delay: 0.2 }, - { className: 'absolute left-56 top-1/2', delay: 0.2 }, - { className: 'absolute bottom-20 left-20', delay: 0.2 }, - { className: 'absolute -top-16 right-20 md:top-10', delay: 0.6 }, - { className: 'absolute bottom-20 right-44', delay: 0.6 }, - ] - - return ( - <> - {starConfigs.map((config, index) => ( -