Home page 'Three Steps' section renders only 2 of 3 cards on first paint — Framer Motion + IntersectionObserver race #104
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#104
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: "From Image to Stitches in Three Steps" section only renders 2 of 3 cards on first paint
Severity
Medium — first-impression regression on a marketing section the visible copy promises is 3 steps. Affects every landing-page visit until the section's intersection observer fires.
Reproduction
/(home page).Expected: 3 cards (01 Upload, 02 Choose, 03 Download).
Actual: only 2 cards visible (01 Upload, 02 Choose); the 03 Download card is missing.
Evidence
/tmp/section_2.png(visual): captures the section with steps 01 and 02 rendered, the third column empty despite the heading promising "Three Steps".Vision model on the screenshot:
Source
src/pages/Home.tsx:351-366defines all three steps in the source:The render loop at
src/pages/Home.tsx:393-433:inViewis a Framer MotionuseInViewflag driven by the IntersectionObserver on the section ref. Step 3's animation hasdelay: 0.24sand only starts after the observer fires.Root cause (best guess)
Framer Motion
useInViewdefaults toonce: falseand is observed against the section's bounding box. For a 480px-tall section, the third-column card (right edge) sits inside the section's viewport, but the intersection observer fires only when the section enters the viewport — not when each card individually does.Result: when the user lands on the page (no scrolling yet) and the section is just below the fold, the observer fires with the bottom of the section visible but
inViewreports partially-true. Step 3 has the longest delay (0.24s) and is the most vulnerable. If the screenshot was taken in the animation window, step 3 is still atopacity: 0, y: 30.A user who reloads the page with the section partially in view at the moment of first paint will see a 2-card section permanently until they scroll past it and the observer re-fires.
Suggested fix
whileInViewwithviewport={{ once: true, amount: 0.2 }}so the trigger is per-element and only runs once.opacity: 1, y: 0and animate only the cards that are still off-screen.Related
useInView-driven entry animations.Fixed in
df02208(push 81fd94f →df02208after rebase on top ofe15556f).Home.tsxlines 396-407. Switched the per-cardmotion.divfrom the shared-sectionanimate={inView ? … : {}}flag towhileInView+viewport={{ once: true, amount: 0.2 }}. The trigger is now per-element (each card animates the first time its own bounding box is 20% visible), so step 3 no longer gets stuck waiting on the section-level observer timing.once: truekeeps it one-shot — re-scrolling through the section does not replay the entry animation.Left the section-level
inViewin place for the header and connecting-lines motion, which are not affected by this race.Verified locally:
tsc --noEmitclean,eslint src/pages/Home.tsxclean. Push landed onorigin/mainasdf02208.