Home page hero + feature/step sections render as empty gray gradient — IntersectionObserver animation never fires, content stays at opacity 0 #107
Labels
No labels
blocked:commit-ready-failed
blocked:gate-fetch
blocked:gate-lint
blocked:gate-plan
blocked:gate-research
blocked:needs-attention
blocked:needs-decision
blocked:needs-human
blocked:no-changes
blocked:verify-failed
bug
deferred
feature-request
in-progress
needs-decompose
needs-triage
quickfix
security/medium
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/Kimi_Agent_Brother_10_Digitizing_App#107
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bug: Home page hero
<motion.h1>content never paints — framer-motionanimate={{...}}direct-animate fails on first mount in React 19.2 + framer-motion 12.40Reproduction
cd ~/Projects/Kimi_Agent_Brother_10_Digitizing_App && npm run devhttp://localhost:3000/in a fresh browser tab./hero-stitch-animation.mp4to fully load and decode).Expected
The hero section is
min-h-[100dvh]with a video background + dark overlay. Centered content rendered via framer-motionmotion.p / motion.h1 / motion.div:#E6A948): "EMBROIDERY DIGITIZING FOR BROTHER ENTREPRENEUR"#F8F2E9): "Turn Any Image Into Perfect Stitches"Actual
At scrollY=0, the hero is empty gray gradient. Header (navbar) and the "Service unavailable" alert banner render normally. Below the banner, the entire viewport (~993px tall) is empty gray gradient. Eyebrow, headline, sub-headline, gradient underline, CTAs, and background video all invisible.
Verified with
mcp__browseros__screenshotat t+2s, t+3s, t+6s, t+10s — content never appears. Cross-verified by navigating to/workspace(renders correctly) then back to/— hero still invisible. Cross-verified with a fresh tab + cache_bust query string — same result.mcp__browseros__read(text extraction, scrolls internally) returns the full hero text correctly — DOM contains all the content. So this is a paint/animation failure, not a render failure.Why this is NOT a duplicate of #104
#104 ("Three Steps section renders only 2 of 3 cards on first paint — Framer Motion + IntersectionObserver race") was closed with commit
df02208(per the closing comment "Fixed in df02208"). That commit switched Three Steps from shared-sectionIntersectionObserver + inViewto per-cardwhileInView. Verified onmain:src/pages/Home.tsxnow has onlywhileInViewcalls, nosetInView/ shared IntersectionObserver remaining. Three Steps now renders correctly.The hero does NOT use
whileInViewor IntersectionObserver. It uses framer-motion's directanimate={{ opacity: 1, y: 0 }}pattern with delays (200–900ms). This should ALWAYS run on mount, not on scroll-into-view. The fact that it doesn't render indicates a different failure mode — likely React 19 + framer-motion 12 direct-animate not running on initial mount, OR the hero<section>z-index/layout container is hiding painted content.Root cause hypotheses
package.jsonshows"react": "^19.2.0"and"framer-motion": "^12.40.0". framer-motion 12 added LazyMotion + new mount semantics; React 19 changed concurrent rendering behavior. There's a known interaction wheremotion.X initial={{opacity:0}} animate={{opacity:1}}doesn't trigger the entry animation when the parent component renders inside a route transition.<video autoPlay loop muted playsInline>atsrc/pages/Home.tsx:43-53is positionedabsolute inset-0 w-full h-full object-coverwith amin-h-[100dvh]parent. The 6.6 MB MP4 may be blocking the main thread during initial decode, delaying React's commit phase past the framer-motion mount tick.video{aspect-ratio:16/9}from Tailwind preflight, found indist/assets/*.css) may be conflicting with the hero video'sw-full h-full object-coverclasses, causing the video to render at aspect-ratio size instead of full-bleed — which in turn would expose the dark overlay as a small rectangle and the rest of the section as transparent (showing through to body's neutral background = gray).Suggested fix scope
initial={{opacity:0}} animate={{opacity:1}}toinitial={{opacity:1}}+whileInView={{opacity:1}}if the entry animation isn't load-bearing; OR keep the entry animation but addviewport={{once:true}}and explicitly trigger on mount viauseAnimation().start().<source poster="/hero-still.jpg">so first paint shows a poster frame even if the video is decoding. Verify with a smaller video (compressed to ~500 KB).Home.tsxin jsdom with a stubbedIntersectionObserver+HTMLVideoElement.play = vi.fn(), assert that the hero<h1>has computedopacity: 1after 1 second.Labels
Severity
High. This is the FIRST page a new user sees. With the hero invisible, the page presents as a half-finished stub: header chrome + alert banner + empty gray gradient. A first-time visitor cannot tell what the product does, doesn't see the value proposition, and has no path to engage. Critical path user-story failure on the marketing surface.
Re-opening and rescoping after the auto-close daemon saw the IntersectionObserver overlap with #104.
This issue is distinct from #104:
IntersectionObserver + setInViewrace. Fixed indf02208by switching to per-cardwhileInView.<motion.h1>etc. uses framer-motion directanimate={{opacity: 1}}with delays (200–900ms), NOTwhileInView. The hero content never paints even after 10s wait. Verified that all other sections on Home now usewhileInViewand are correct — only the hero is broken.Re-titled and re-bodied to reflect the actual novel finding. Repro evidence preserved in screenshots taken 2026-09-25 against
main@1a31160.— automated cleanup, 2026-09-25