Repository navigation
feat(web): phase 9 scroll and wallet motion (#884-#887) - #1003
Merged
IbrahimIjai merged 3 commits intoOct 19, 2026
Merged
Conversation
…ets#887) Implements the four assigned motion issues: - SO4-Markets#884 scroll-aware header. New useScrollHeader hook coalesces scroll work into one animation frame and drives a transform-only hide/show on mobile plus a data-scrolled hairline that fades in as a composited layer. Applied to the landing HeaderMenu and the app Navbar; only the landing header compresses its height, because it is fixed over the hero and cannot move document content (no CLS). - SO4-Markets#885 scroll-driven section reveals. ViewportReveal now uses CSS scroll-driven animations (animation-timeline: view()) where supported, running off the main thread, and keeps the IntersectionObserver path as the fallback. Children stay in document flow and in the accessibility tree in every mode. - SO4-Markets#886 count-up statistics. New CountUp eases landing stats from 0 the first time they enter the viewport, keeps tabular figures so the row never reflows, and announces only the final value to screen readers. Null stats still render the "-" placeholder with an "Unavailable" accessible label. - SO4-Markets#887 wallet connect choreography. Wallet rows stagger in, the in-flight connect trigger becomes an aria-busy status with a single pulsing indicator, and the connected pill animates in only on the connecting -> connected transition rather than on every render. All motion is token-driven (new --duration-pulse) and transform/opacity only, respects prefers-reduced-motion and the in-app reduced-motion toggle, and is interruptible. apps/docs/content/reference/tokens.generated.mdx is regenerated for the new token. Verification on this branch (see the PR for the full gate report): - bun lint: 10 pre-existing errors, all in files this change does not touch (verified identical on main before the change). Zero errors in changed files. - bun typecheck / apps/web typecheck: 1 pre-existing error (useTxSubmit.tsx: unused 'attempt'), present on main. - bun run check:content: passes. - bun run check:tokens: 14 pre-existing violations (SO4-Markets#732/SO4-Markets#733 comments and an HTML entity in apps/docs), all present on main; none in changed files. - bun run test / test:coverage: 1 pre-existing failure (packages/ui popover.test.tsx data-open:animate-in), present on main. - bun run build: passes (apps/docs, apps/web, packages/ui). - app tests for the changed behaviour: 5 files, 56 tests, all passing. - apps/s03-indexer codegen/build/test (100 pass), sync:contracts:local, scripts/validate-manifest.sh, apps/web build: pass. - bun run changelog:validate is already red on main for unrelated entries; the entry for this change is added in a follow-up commit once the PR number exists.
…SO4-Markets#884) The scroll-aware header now carries `transform: translateY(-100%)` while hidden on mobile. A transformed ancestor becomes the containing block for fixed descendants, so the full-screen menu (`fixed inset-0 top-16`, nested in the header) resolved its `inset-0` against the 64px header box and collapsed to zero height. The e2e landing journey caught it at 390px as `dialog "Site menu"` resolving to hidden. Move the panel to a sibling of `<header>` so the viewport stays its containing block, and add a component test that fails if the panel is ever nested back inside the header. Verified: e2e/landing-journey.spec.ts at 390px now passes the menu open and Escape assertions; the remaining failures in that spec (trade "Long" tab not visible, one running animation after route exit) are identical on main.
|
@hameedx01 is attempting to deploy a commit to the Ijai's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@hameedx01 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Unreleased entry for the Phase 9 motion change, now that the pull request number exists. The file follows .changelog/unreleased/TEMPLATE.md; bun run changelog:validate reports no problem with this entry (it is still red on pre-existing, unrelated entries).
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #884
Closes #885
Closes #886
Closes #887
Implements the four Phase 9 motion issues in one change. All motion is
token-driven,
transform/opacityonly, honoursprefers-reduced-motionandthe in-app reduced-motion toggle, and is interruptible. No
eslint-disable,.skip, or lowered threshold was added.What changed
#884 — scroll-aware header (
apps/web/src/shared/hooks/useScrollHeader.ts,ui/landing/header-menu.tsx,ui/Navbar.tsx)useScrollHeaderhook coalesces all scroll work into a singlerequestAnimationFrame, tracks a scrolled flag (8px) and a direction flag(6px threshold so jitter cannot flip it), and only hides on mobile
(
max-width: 639px). The header is always revealed at the top of the pageand on desktop.
transform: translateY(-100%); nothing changes document height, sothere is no layout shift. The hairline and shadow are drawn by a pseudo
element that fades in with
opacity, replacing the layout-affectingborder-b border-borderon the app navbar.fixedover the hero and cannot move document content. The app navbar keepsits height so nothing under it can shift.
interactions are CSS transitions on the compositor.
<header>: a transformed ancestorbecomes the containing block for
fixeddescendants, which collapsed thefull-screen panel to zero height. This was caught by
e2e/landing-journey.spec.tsat 390px, and there is now a component testthat fails if the panel is nested back inside the header.
#885 — CSS scroll-driven reveals (
ui/viewport-reveal.tsx,lib/supports-view-timeline.ts,lib/use-viewport-reveal.ts)ViewportRevealusesanimation-timeline: view()withanimation-range: entry 10% entry 90%where the browser supports it, so thereveal runs off the main thread. Browsers without view timelines keep the
existing IntersectionObserver path (
useViewportRevealgained anenabledflag so the observer is not constructed when CSS drives the animation).
and in the accessibility tree in every mode; nothing is measured or unmounted.
#886 — count-up landing stats (
ui/landing/count-up.tsx,ui/landing/hero-section.tsx)CountUpeases each stat from 0 the first time it enters the viewport,using
--duration-slowestand--ease-out(cubic-bezier(0, 0, 0.2, 1), whoseshape is matched by
easeOutCubic).font-variant-numeric: tabular-nums slashed-zeroso digitsnever reflow the row, and the animating number is
aria-hiddenwhile a singlesr-onlynode carries the final formatted value — screen readers get oneannouncement, not one per frame.
null; those still render the-placeholder with anUnavailableaccessible label, and the animation isskipped.
#887 — wallet connect choreography (
features/wallet/components/ConnectButton.tsx)--wallet-staggercustom property (index × 35ms,capped by the token duration), transform and opacity only.
aria-busy="true"status button witha single pulsing indicator and a
role="status"label, so the click hasimmediate feedback and the surrounding layout does not move.
connecting → connectedtransition (tracked with a ref), not on every render of an already-connected
wallet.
Tokens — one new token,
--duration-pulse: 1500ms(the 1.5s loop in themotion spec).
apps/docs/content/reference/tokens.generated.mdxis regenerated.Verification
Commands run on
feat/phase-9-motion-884-887(base5932835), withbuninstalled at the version pinned in
bun.lock.bun lint5932835). Zero errors in changed files.bun typecheckuseTxSubmit.tsx(125,13): 'attempt' is declared but its value is never read), present on base.bun run check:tokens#732/#733comments read as hex colours, one HTML entity inapps/docs), all present on base, none in changed files.bun run check:contentbun run test/test:coveragepackages/ui/src/components/popover.test.tsxexpectsdata-open:animate-in), fails identically on base.bun run buildapps/docs,apps/web,packages/ui).apps/webtypecheck / buildapps/webtests for changed behaviouruseScrollHeader,count-up,viewport-reveal,use-viewport-reveal,header-menu,ConnectButton).apps/webfull suiteapps/s03-indexercodegen, build, 100/100 tests,sync:contracts:local,scripts/validate-manifest.shall pass.bun run changelog:validateob-119-render-coalescing.md,ob-061-chart-resize.md,780-ob-125-safe-caching-headers.md, …). The entry for this change is added in a follow-up commit once this PR number exists.CI is red on
mainat the base commit for the same reasons — the workflowrun for
5932835fails at the Lint step with exactly the same 10 errors, so thePlaywright E2Ejob never runs there. This PR does not add any of them, andfixing unrelated lint violations here would be out of scope.
Layout-shift evidence (
PLAYWRIGHT_LANDING_AUDIT=1, production testnet build, Chromium)All values are far below the 0.1 budget; the differences between runs are at the
noise floor of this lab measurement, not a shift introduced by the header.
Recordings
Six before clips and six after clips (390/768/1440 × normal/reduced motion) were
recorded from the same spec with
PLAYWRIGHT_LANDING_AUDIT=1 PLAYWRIGHT_MOTION_EVIDENCE=1. They are attached tothis PR via the GitHub web UI (the API cannot upload PR attachments); the local
copies live in
~/so4-motion-evidence/before/and~/so4-motion-evidence/after/and are not committed, per the convention in
docs/gf_3/003_landing_motion_review.md.The remaining failures in
e2e/landing-journey.spec.ts(getByRole('tab', { name: 'Long' })not visible on
/trade, and one running animation after route exit) areidentical on base and are not touched by this change.