gainforest-app · mobile-responsive-pass

Mobile responsive pass on the GainForest landing

@daviddao

Hardened the landing for phones — kept the live Bumicerts and Globe windows visible (no hiding), shrank the typography, mounted a mobile nav drawer, and re-anchored the floating capybara — while leaving the desktop draggable-windows layout untouched.

Date: 2026-05-18 · Target: http://127.0.0.1:3030 (Next 16.1.6) · pass


Headline result

7
Issues fixed
3
Breakpoints tested
2
Live windows visible on phone
0
Regressions on desktop

Headline. At 390 × 844 (iPhone 13/14/15) the landing now reads beautifully end-to-end: hero fits, both Bumicerts and Globe windows render full-width inline beneath the hero copy (instead of overflowing or hiding off-screen), a slide-in nav drawer covers the 5 hidden links, and the floating Capybara flips to bottom-right so it doesn't sit on top of the single-column content stream. Desktop (≥ lg, 1024 px) keeps the original draggable-windows layout.

Before / after

Same viewport (iPhone 14 size, 390 × 844), same scroll position. The before-shots show why the page needed a pass: 78 px hero headline doesn't fit, the 400 px Bumicerts card overflows the right edge, and the 280 px Globe card is pushed entirely off-screen by its right:-35px desktop offset.

Mobile hero before — 78px headline overflows, Bumicerts card spills off the right edge, Globe card off-screen, no nav menu.
BeforeHero @ 390 px. Headline at 78 px wraps to four single-word lines. Bumicerts card (400 px) overflows the viewport on the right. Globe card invisible (it's at right:-35px, pushed past the viewport edge). Capybara sits on the headline.
Mobile hero after — 42px headline fits in two lines, full-width CTAs, capybara moved to bottom-right, Globe card inline below.
AfterHero @ 390 px. Headline at 42 px reads in two natural lines. CTAs stack vertically and stretch full-width. Globe card appears inline beneath the copy with its full chrome (logo + "Globe" + LIVE badge). Capybara flips to bottom-right; "Tanya saya apa saja" name shield intact.

Live data, inline (where it used to be hidden)

The two windows aren't shrunk into a placeholder — they're the same live data the desktop floating windows show, just rendered inline (no position: absolute, no drag) at max-width: 420px. The Bumicerts card retains its full rail (Proyek / Organisasi / Papan peringkat), the live search bar, three live project rows pulled from the indexer, and the footer count.

Mobile hero bottom before — empty space + tall leaves decoration, both cards hidden somewhere off-screen.
BeforeScrolling past the hero copy reveals only the leaves decoration filling the right column. Both live windows are still positioned absolutely on the (now non-existent) desktop right-column anchors, so they overflow or hide.
Mobile after — Globe sphere and Bumicerts card stacked inline with full chrome.
AfterLive Globe (with project pin dots) and live Bumicerts card (3 real indexer-sourced rows: Meru / Mosaic Village / Samos) stack vertically with matching cream chrome and "LIVE" badges.

Walkthrough — every section, mobile (390 × 844)

One scroll through the page after the pass:

ChoosePath section on mobile.
ChoosePathThe "Pilih cara Anda menggunakan GainForest" section stacks the two CTAs vertically with the live mini-globe between them and the "atau" italic separator beneath. The mini Bumicerts strip keeps three real thumbnails.
I want to… cards on mobile, single column.
I want to…Section heading drops from 44 → 32 px. The four cards stack into a single column with looser height (min-h-[200px]) and tighter padding (p-5 vs desktop p-7).
How it works steps stacked vertically on mobile.
How it worksThe four-step strip (Temukan → Pahami → Dukung → Tumbuhkan dampak) stacks vertically. The horizontal "→" arrows hide below lg (they only make sense in the horizontal layout).
Nature thrives CTA banner on mobile — full-width primary button, ghost link below.
Nature CTA"Alam berkembang ketika kita bertindak bersama" drops from 36 → 28 px. Buttons stack: full-width primary, ghost link below. Banner padding drops to px-6 py-7.
Footer on mobile — logo + copyright on one row, nav links wrap on a second.
FooterFooter drops from flex justify-between to flex-col; logo + copyright on one row, nav links (Globe / Bumicerts / GitHub / Kontak) wrap on a second row instead of spilling off the right.
Mobile nav drawer open — 5 nav links, sign-in, Get started CTA.
Mobile drawerTap the hamburger → a 280 px right-anchored drawer slides in with all 5 nav links in Garamond, a sign-in pill, and the "Mulai" CTA. Backdrop darkens with a 2 px blur. Esc / tap-outside closes it.

Other breakpoints — sanity

The same component set, rendered at tablet (768 px) and desktop (1280 px) widths. The split point is Tailwind's lg (1024 px): below it everything goes inline, at or above it the floating draggable windows return.

Tablet 768px — hero with side-by-side CTAs, Globe card inline below.
Tablet (768 × 1024). Hero scales up to 58 px headline, CTAs go side-by-side again (sm:flex-row), inline cards centre at max-w-[420px]. Hamburger is still visible since we're below lg.
Tablet 768px — Bumicerts card inline, ChoosePath section beneath.
Tablet, scrolled. Bumicerts inline card retains the full rail / search / 3 rows / footer layout. ChoosePath renders beneath unchanged.
Desktop 1280px — original draggable Globe + Bumicerts windows untouched.
Desktop (1280 × 800). The original draggable, document-positioned windows are back. Hero typography hits 78 px, the leaves decoration appears between the column gutter, and the Capybara defaults to bottom-LEFT to balance the right-weighted hero.

Findings

Hero typography now fits the phone

Headline: text-[42px] sm:text-[58px] lg:text-[78px]. The 42 px mobile size wraps the two i18n lines naturally instead of forcing one-word lines.

Subtitle and footnote also step down (text-[15px] lg:text-[16px] / text-[12px]). Hero padding drops to px-6 pt-10 on mobile vs px-16 pt-16 on desktop.

Live windows render inline on mobile (never hidden)

Both BumicertsCard and DraggableGlobeCard now accept an inline prop. In inline mode they skip the useDraggableDocPos hook, drop position: absolute, and cap to max-width: 420px. The same live data feeds the desktop floating cards and the mobile inline cards — no separate mock.

Composition: app/page.tsx renders both flavours and toggles between them with Tailwind responsive utilities: <div className="hidden lg:block"> for the draggable absolutes; the inline pair is passed to <Hero> via an inlineCards prop and rendered inside the hero column with lg:hidden.

Mobile drawer for the 5 hidden nav links

The desktop navbar's 5 links (Globe / Bumicerts / Untuk komunitas / Untuk pendukung / Tentang) were behind hidden md:flex, which on phones simply hid them with no replacement.

Added a hamburger button (lg:hidden on the trigger, hidden lg:flex on the desktop link strip) that toggles a right-anchored drawer with all five links, the sign-in pill, and the primary CTA. Esc and backdrop-tap close it; body scroll is locked while open. Animation: 180 ms drawerIn keyframe.

Capybara default flips per viewport

Desktop default stays bottom-LEFT (balances the right-weighted hero windows). Mobile default flips to bottom-RIGHT — on a single stacked column a bottom-left mascot constantly overlaps the content stream as you scroll.

Detection: window.innerWidth >= 1024 at first paint. Persisted position survives in localStorage (key bumped to v3 so existing visitors pick up the new default).

Decorative weight tuned for small screens

Hero's leaves bouquet is hidden lg:block now — at 390 px it was taller than the hero and dominated the page. Section vertical padding drops from pt-14 to pt-10 below lg across IWantTo / HowItWorks / NatureCTA / ChoosePath. Section headings step down (text-[32px] sm: text-[40px] lg:text-[44px]).

Footer reflows on narrow screens

Was flex items-center justify-between in a fixed-width row, which spilled "Bumicerts" off the right edge below ~500 px. Now flex-col items-start gap-4 lg:flex-row with the nav links allowed to flex-wrap.

NatureCTA banner buttons stack on mobile

The "Jelajahi proyek" + "Buat Bumicert →" pair was a horizontal flex that crowded against the right edge of the banner. Now flex-col sm:flex-row with the primary button taking full width on phones.

Desktop layout untouched (no regressions)

Every responsive change is gated on lg: utilities so the existing draggable-windows hero, the column-gutter leaves bouquet, the four-step horizontal "How it works" strip, the horizontal footer, and the bottom-LEFT default Capybara are all unchanged at ≥ 1024 px. pnpm exec tsc --noEmit is clean.

Design tokens & class conventions

Everything stays inside the existing design system from app/AGENTS.md — no new colours, no CSS-in-JS, no Tailwind config changes. Mobile work is pure responsive utility classes plus two new constants:

Breakpointlg = 1024 px (Tailwind default). Below: stacked inline layout. At/above: floating draggable cards. Hero font scaletext-[42px] sm:text-[58px] lg:text-[78px] Section heading scaletext-[32px] sm:text-[40px] lg:text-[44px] Hero paddingMobile px-6 pt-10 → Tablet sm:px-10 → Desktop lg:px-16 lg:pt-16 Inline card max-width420 px (matches the BumicertsCard chrome at desktop width) Capybara defaultDesktop bottom-LEFT (x=32, y=H-122) · Mobile bottom-RIGHT (x=W-102, y=H-132) Drawer animationdrawerIn 180ms ease-out — slide-in from translateX(20px) + fade

Reproducing

Repogainforest/gainforest-app (main) Target URLhttp://127.0.0.1:3030 (Next 16.1.6, Turbopack, pnpm dev) Mobile viewportagent-browser set viewport 390 844 Tablet viewportagent-browser set viewport 768 1024 Desktop viewportagent-browser set viewport 1280 800 Type-checkpnpm exec tsc --noEmit (clean) Walkthrough videomobile-walkthrough.webm (mobile scroll + drawer) Screenshotsassets/01-before-*.png · assets/10-after-*.png · assets/2x-after-tablet-*.png · assets/30-after-desktop.png

Files touched: app/_components/Hero.tsx · BumicertsCard.tsx · DraggableGlobeCard.tsx · TopNavView.tsx · SignInPopover.tsx · FloatingCapybara.tsx · NatureCTA.tsx · Footer.tsx · IWantTo.tsx · HowItWorks.tsx · ChoosePath.tsx · page.tsx · globals.css (drawerIn keyframe).