Skip to content

Let visitors scroll and drag the landing page project gallery - #8

Merged
MorganGodden merged 1 commit into
masterfrom
claude/landing-gallery-scroll-drag-mb9rsp
Aug 10, 2026
Merged

MorganGodden merged 1 commit into
masterfrom
claude/landing-gallery-scroll-drag-mb9rsp

Conversation

@MorganGodden

Copy link
Copy Markdown
Owner

The gallery on the landing page moved on rails. UMarquee animates its content in CSS, and a CSS animation is not a scroll position, so a trackpad swipe, a touch drag or a mouse drag had nothing to take hold of — anyone wanting a second look at a card that had just gone past had to wait for it to come round again.

What changed

A new DraggableMarquee lays the same repeated content out in a real horizontal scroller and drifts it by nudging scrollLeft a fraction of a pixel each frame. That makes the drift one more writer of a position the visitor can write to as well, so scrolling, swiping, dragging, momentum and the keyboard all work because the browser already does them rather than because they were reimplemented.

The infinite loop is the usual trick: the slot is repeated end to end, and since the copies are identical, shifting the scroll position by exactly one copy's width is invisible. Doing that whenever the position leaves the middle copy keeps a whole copy of slack either side, so the strip never reaches an end to stop or bounce at. The copy count adapts to how wide the strip is.

landing/Hero.vue swaps UMarquee for it. The card markup and its Motion animations are untouched.

When it holds still

The drift stands aside while the gallery is being used: under a resting pointer, during a drag, while a card is focused by the tab key, for two seconds after any scrolling of the visitor's own, off screen, and entirely for anyone who asks for reduced motion.

Two details there took a second pass. Pressing on a card focuses it, so holding the strip for anything focused inside it meant a drag stopped the gallery for good — the card the drag began on kept the focus long after the pointer had gone. :focus-visible is the platform's own answer to whether someone tabbed here or pressed here, and asking it lets a keyboard visitor keep the strip while a press no longer takes it. A pointer left sitting where a drag ended no longer holds it either: resting on a card to look at it still does, but after a drag the pointer is wherever the drag ran out rather than over anything the visitor meant to stop on.

Two things worth a look

Mouse-wheel scrolling is not hijacked. Trackpad horizontal scroll, shift+wheel and touch swipe all scroll the strip natively, but a plain vertical wheel still scrolls the page rather than the gallery. Capturing it would trap the page under a hero element. Dragging is the mouse affordance instead, with a grab cursor. Happy to change this if you would rather the wheel drove it.

The duplicated copies are not inert. That would have been the tidy way to hide them, but inert also refuses clicks, and the copy under the visitor's pointer is nearly always a clone — it made the visible cards unclickable. They are hidden from assistive technology and taken out of the tab order instead, which says the same thing while leaving them clickable.

Testing

Driven in Chromium against a production build.

Drift rate 40.0 px/s, matching the speed prop
Drag moves the strip both directions; wraps rather than hitting an end
Touch and programmatic scroll scroll natively and wrap
A drag ending over a card does not navigate
A plain click on a card navigates to /projects
After a drag, pointer left resting still at 0–1s, resumes in the 1–2s window, 42 px/s by 2–3s
Plain hover, no drag 0px over 6s; moves again once the pointer leaves
Card focused by Tab 0px over 3s; drifts again once focus moves on
Wheel scroll still at 0–1s, 41 px/s by 2–3s
prefers-reduced-motion 0px
Tab order 5 of 15 links tabbable, clones aria-hidden
Console no errors

typecheck and build pass. lint reports the same 41 problems with and without this change — all pre-existing, in app.vue and about.vue.

The remote itch.zone project images cannot load from the sandbox this was built in, so they showed as alt text in my screenshots. That is environmental and not a change in behaviour.


Generated by Claude Code

Dragging the gallery stopped it for good. Pressing on a card focuses it,
and the drift held still for anything focused inside the strip — so the
card the drag began on kept both the focus and the strip long after the
pointer had gone elsewhere.

That hold was meant for someone working through the cards with the tab
key, who would otherwise have to read a card that was wandering off.
`:focus-visible` is the platform's own answer to which of the two just
happened, and asking it lets a keyboard visitor keep the strip while a
press no longer takes it.

A pointer left sitting where a drag ended no longer holds the strip
either. Resting on a card to look at it still does, but someone who has
just dragged has said where they want the gallery, and their pointer is
wherever the drag ran out rather than over anything they meant to stop
on. Leaving and coming back arms the hold again.

The wait before the drift resumes is now the two seconds asked for,
and it no longer applies to the moment the page loads, where a fast
first paint could leave the gallery sitting still until the clock it is
measured against had passed two seconds of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015aCGjKwtBpk8NdVKxQiCRA
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
godden.dev Ready Ready Preview Aug 10, 2026 2:51pm

@MorganGodden
MorganGodden merged commit 157ce3e into master Aug 10, 2026
2 of 3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 793ba6b8 Deployed Aug 10, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants