chore(marketing): consolidate the stray pages, and give /careers a real one - #2767
chore(marketing): consolidate the stray pages, and give /careers a real one#27670xkkonrad wants to merge 6 commits into
Conversation
…ions only /careers was a 30-word interstitial: a mascot, one sentence, and a link out to Notion. No <h1>, no footer, no Crisp, and metadata still selling 'Instant Global P2P Payments' from the protocol era. The footer's Jobs link bypassed it entirely, pointing at a second URL for the same Notion page. The page now carries its own content — what Peanut is, how the work works, and the open roles — sourced from content/press/en.md so nothing is invented. Notion is reached only by 'Apply on Notion'. The footer Jobs link now points at /careers instead of routing around it. Uses the public *.notion.site URL; the old www.notion.so workspace URL bounced logged-out visitors to a login.
Peanut's Rain resubmission note records the '$10 after $100 card spend'
activation reward and the '$10 / $10 referral' as removed everywhere they
appeared. Both were still live on /shhhhh in all four locales, and the same
claim was live in the app home carousel.
- shhhhh.faq.q4 no longer carries the reward. The FAQ list is a hardcoded
q1..q8 array, so the entry is replaced rather than deleted — renumbering
four catalogs to remove one answer buys nothing. It now answers identity
verification, which Rain's own guidance states.
- shhhhh.faq.q6 moves from issuance framing ('rolling out to select regions
first') to acceptance framing, per the rule that public card copy describes
acceptance and never availability.
- home.carousel.card.description drops the same reward clause.
The es-AR override for the carousel is deleted rather than rewritten: it only
existed for a voseo imperative inside the reward clause, and es-AR must stay a
strict subset of es-419 or it silently stops inheriting fixes.
Peanut's general referral share is untouched — the ruling keeps it, because it
is not tied to the card.
Four marketing routes that carried no unique content, all now permanent redirects: - /lp/card -> /shhhhh. Dead behind disableCardPioneers: 200 with full 'Card Pioneers' metadata and zero body copy, while sitting in the sitemap at priority 0.9. Matches PR #2690's destination and wording so the two do not diverge. - /exchange -> /. Rendered the same NoFees component already on the homepage plus a footer, shipped an empty client shell, and its title promised 'Live Exchange Rates' while its H1 said 'ZERO TRANSFER FEES'. NoFees itself is untouched and still used by the homepage and /quests. - /:locale/blog -> /:locale/content. Byte-identical to /content with fixedType='blog'. Only the index is gone; all 21 posts survive. - /:locale/team -> /:locale/press. /press already renders the same two member cards from the same source, with real photos. Fallout cleaned up in the same pass: robots.ts no longer allows two routes that are now 308s; CardPioneers pushes to /shhhhh instead of client-navigating into a redirect; and the blog post breadcrumb — which feeds a BreadcrumbList JSON-LD — now points at /content?type=blog rather than naming a redirecting URL. That form matches how /compare and /use-cases already link back, and keeps the crumb reading 'Blog'. Also drops the now-dead 'team' entry from ROUTE_SLUGS and refreshes comments that cited the retired routes as live.
…root identity - /crisp-proxy, /kyc/success and /maintenance defined no metadata, so all three inherited the root layout's canonical: '/' and declared the homepage as their canonical while serving index, follow. Each is a client component, so metadata cannot live in the page — added sibling layouts carrying a self-canonical plus robots noindex, the pattern /exchange already used. - Root title was 'Peanut - Instant Global P2P Payments in Digital Dollars', protocol-era framing inherited by every page without an override. The root description said the same thing. Both now describe the money app. - The X handle @peanutprotocol appeared in six places while the footer links x.com/joinpeanut. All six now say @joinpeanut, including the Organization JSON-LD sameAs and llms-full.txt. - Adds the five card legal pages to the sitemap. They return 200 with index, follow and were listed nowhere. English only: the localized copies serve English and already canonicalize to /en, so listing all twenty would create fifteen duplicates competing with their own canonical.
…ate, localize /press
- /en/content and /en/blog emitted zero article links in server HTML while the
sitemap listed 21 posts. ContentLanding is a client component read through
nuqs, so Next prerenders the Suspense fallback — and the fallback was a
pulsing skeleton. The list rendering is now an exported ContentLinkList that
touches no URL state, and the fallback renders it, so the links exist in the
prerendered HTML. Interactive filtering is unchanged.
- Content cards rendered the raw frontmatter title, leaking the SEO suffix into
the UI ('Lynn Pham: The Brand Girl | Peanut'). Stripped at render time only;
sitemap and metadata still read the raw value.
- /en/stories linked to /en/stories/index, a live second copy of the hub with a
self-referencing canonical and different hero copy. The page now applies the
same slug === 'index' guard the sitemap already had.
- /press rendered all six section headings as hardcoded English on a [locale]
route. They now come from the marketing catalogs, translated in all four.
- /press absorbs the team data: its Organization JSON-LD gains the member[]
array that only /team used to emit, so deleting /team does not drop the
site's only Person schema.
Drops the now-unused fixedType prop and the teamTitle/teamSubtitle keys, both
orphaned by the routes retired in this branch.
…nce and the waitlist
The ragdoll had no prefers-reduced-motion handling at all. It now follows the
same shape as confetti.ts — matchMedia read live per mount — and degrades
rather than disappearing: the world is stepped off-screen until the peanut
settles, then painted once, with no RAF loop and no pointer listeners.
Two new homes, both non-scrolling panes with a definite height:
- /maintenance replaces a static crying-peanut image. The copy was the worst
voice on the site ('We are currently going through maintenance... Sorry for
the inconvenience') and a button labelled 'Contact Support?'. Rewritten to
match the 404's register.
- The waitlist jail pane, gated on step === 'jail' so plain /invite — a signup
conversion surface — does not get a draggable toy next to its CTA. The
illustration pane moves from min-h to a definite h-[55dvh]; min-h gives
h-full nothing to resolve against, so the canvas would have silently
collapsed to zero height while the loop kept running.
Fixes a real bug found while placing it: the (mobile-ui) layout registers
pull-to-refresh on document before the waitlist early-return, and its
#scrollable-content guard is absent on that screen, so the handler fell
through to true. Dragging the peanut down ~160px would have reloaded the page
and wiped the user's typed invite code. The host now swallows touchstart — a
native listener, because React hydrates on document and a synthetic
stopPropagation would not stop a same-target sibling.
startRagdoll now runs in a try/catch: it throws synchronously on a failed 2D
context, which would white-screen /maintenance, the one page whose job is to
work when nothing else does.
Also retires the stale 'triple-click Rewards title' comments — that easter egg
was deliberately cut in 347757d and never shipped.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code-analysis diffPainscore total: 7289.31 → 7275.96 (-13.35) 🆕 New findings (47)
…and 27 more. ✅ Resolved (74)
…and 54 more. 📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
|
Superseded by #2782, which is rebased onto current Two parts of this PR were overtaken while it sat open:
Everything else still applies and is in #2782: the real One correction from this PR's description: the claim that |
Cleans up the stray marketing pages on peanut.me, found in a routing + copy audit of
origin/dev. Six commits, each independently reviewable.Two items from that audit are deliberately not here, per Konrad: the public
/dev/*pages (filed as a Notion task for Jota instead) and the/pricingfee wording.What changes
/careersbecomes a real page. It was a 30-word interstitial — a mascot, one sentence, a link out to Notion. No<h1>, no footer, no Crisp, and metadata still selling "Instant Global P2P Payments". The footer's Jobs link bypassed it entirely for a second URL pointing at the same Notion page. It now has its own content (what Peanut is, how the work works, open roles), sourced fromcontent/press/en.mdso nothing is invented, and Notion is reached only by Apply on Notion. The footer now links/careers./shhhhhdrops the card reward claims. The Rain resubmission note records the "$10 after $100 card spend" activation reward and the "$10 / $10 referral" as removed everywhere they appeared. Both were still live in all four locales — and the same claim was live in the app home carousel. Also moves the "where can I get the card?" answer from issuance framing to acceptance framing, per the rule that public card copy describes acceptance only. Peanut's general referral share is untouched; the ruling keeps it, because it isn't tied to the card.Four routes retired, all permanent redirects:
/lp/card/shhhhhdisableCardPioneers: 200 with full metadata, zero body copy, sitemap priority 0.9/exchange/NoFeescomponent already on the homepage; ships an empty client shell/:locale/blog/:locale/content/contentwithfixedType="blog". Index only — all 21 posts survive/:locale/team/:locale/press/pressalready renders the same two members from the same source, with real photosThe content hub is crawlable again.
/en/contentand/en/blogemitted zero article links in server HTML while the sitemap listed 21 posts —ContentLandingis a client component read through nuqs, so Next prerenders the Suspense fallback, and the fallback was a pulsing skeleton. The list rendering is now an exportedContentLinkListthat touches no URL state, and the fallback renders it.Plus:
/en/stories/index(a live duplicate of its own hub, linked from that hub) is gone;/presssection headings are localized; three client routes stop declaring the homepage as their canonical; the root title and description drop the protocol-era framing;@PeanutProtocol→@joinpeanutin all six places; and the five card legal pages join the sitemap.Ragdoll. It had no
prefers-reduced-motionhandling at all — it now followsconfetti.tsand degrades to a single settled frame rather than vanishing. Two new homes, both non-scrolling panes with a definite height:/maintenance(whose copy was the worst voice on the site) and the waitlist jail pane, gated onstep === 'jail'so plain/invitedoesn't get a draggable toy beside its CTA.One real bug fixed on the way
The
(mobile-ui)layout registers pull-to-refresh ondocumentbefore the waitlist early-return, and its#scrollable-contentguard is absent on that screen, so the callback fell through totrue. Dragging the peanut down ~160px would have firedwindow.location.reload()and wiped the user's typed invite code. The ragdoll host now swallowstouchstart— a native listener, because React hydrates ondocumentand a syntheticstopPropagationwouldn't stop a same-target sibling.Verification
npx jest— 261 suites, 3344 tests, 0 failuresnpx tsc --noEmit— 0 errorsnpx eslinton all 37 changed TS/TSX files — 0 errors;prettier --checkcleanverify-content.ts— Pass 11 green, all 1165 expected URLs have routesgenerateSitemap()executed: 742 URLs, 21 blog posts intact, 0 blog-index, 5 card-legal,/careersstill present/:path*catch-alls are host-scoped todocs.peanut.*and can't shadow theseContentLinkListproven to server-render real anchors (/en/blog/rewards,/en/stories/lynn) with the| Peanutsuffix strippedNot verified locally:
next buildcould not complete — earlyoom SIGTERMs the build worker at ~2.7 GB on this box, with zero compile or type errors in the log before the kill. CI is the real gate for the build. Runtime redirect behaviour also can't be exercised locally: next.config.js:363 deliberately appliesredirects.jsononly outside development.Coordination
/lp/card. Konrad asked for it here too. This branch copies feat(landing): homepage rebuild — persona manifesto, card beat high, /lp/card retired #2690's exact destination, sitemap comment and script edits, so whichever lands second should conflict trivially onredirects.json,sitemap.tsandunderMaintenance.config.ts.CardPioneers.tsxis left in place (now pushing to/shhhhhrather than into a 308) because feat(landing): homepage rebuild — persona manifesto, card beat high, /lp/card retired #2690 deletes it. Note for that PR: deleting it also orphansPioneerCard3D.tsxand sixlandingCard*keys.Follow-ups, deliberately not in this PR
SEOFooterstill links Jobs → Notion fromfooter-manifest.jsonin the peanut-content submodule. That's a content change, and content and code don't travel in the same PR./shhhhh"20 a week" strings predate the ruling recorded inproduct/card.mdthat the pace is now ~50/week — 7 catalog strings, 1 metadata line, and 3 hardcoded literals inScarcityCounter./en/helplooks to have the same client-component crawlability gap: its prod HTML carries only 3/en/help/links./maintenanceand the 404 are still hardcoded English.