Skip to content

Ambient strands behind every hero, and a shimmer border on the yearly plan - #42

Merged
zlshames merged 2 commits into
masterfrom
hero-strands
Sep 18, 2026
Merged

zlshames merged 2 commits into
masterfrom
hero-strands

Conversation

@zlshames

Copy link
Copy Markdown
Member

Two pieces of motion, both deliberately restrained.

Strands

A slow field of luminous ribbons behind the hero, inspired by React Bits' Strands.

Written from scratch on a 2D canvas rather than adding ogl and a WebGL shader. That library is ~25kB gzipped, against a home page whose entire JS budget is about 2kB — a lot of weight for something purely decorative. Brand blues only, low alpha, slow enough to read as ambient.

On all 13 pages: via PageHeader (which ten of them use), plus the home hero and the two pages that build their own (404, privateAPI). The component takes multiple instances, selecting by attribute rather than id.

Behaviour:

  • Animates only while on screen and the tab is visible (IntersectionObserver + visibilitychange)
  • Draws one still frame up front, so it is never blank
  • prefers-reduced-motion keeps the still frame and starts no loop, and a change of preference is honoured without a reload

Shimmer border on the yearly plan

A highlight that travels around the edge of the card. A conic gradient rotates behind it, masked to the border ring with mask-composite, so one highlight runs the whole perimeter, corners included.

React Bits' Star Border slides two radial gradients along the top and bottom edges, which reads as two streaks passing rather than a shimmer going around — the wrong feel for a card, so this is built differently on purpose.

The angle is a registered @property, since a bare custom property can't be interpolated; where that isn't supported the ring simply sits still.

Both

aria-hidden, no pointer events. The shimmer masks itself rather than clipping its parent, so the "Best value" badge overhanging the card is unaffected.

Note on verification

I verified structure, stacking, masking and that the animations register as running — but not the motion itself, because the browser pane here stops compositing and freezes animation clocks. Worth an eyeball before merging.

🤖 Generated with Claude Code

zlshames and others added 2 commits September 18, 2026 16:48
…yearly plan

Two pieces of motion, both deliberately restrained.

Strands: a slow field of luminous ribbons behind the hero, inspired by
React Bits' component of that name. Written from scratch on a 2D canvas
rather than adding ogl and a WebGL shader -- that library is ~25kB gzipped,
against a page whose entire JS budget is about 2kB, for something purely
decorative. Brand blues only, low alpha, and slow enough to read as ambient.

It renders on all 13 pages: in PageHeader, which ten of them use, plus the
home hero and the two pages that build their own (404 and privateAPI). The
component takes multiple instances, selecting by attribute rather than id.

It only animates when it is actually on screen and the tab is visible, via
an IntersectionObserver and a visibilitychange handler, and it draws one
still frame up front so it is never blank. prefers-reduced-motion leaves
that still frame and starts no loop, and a change of preference is honoured
without a reload.

Shimmer: a highlight that travels around the edge of the yearly plan card.
A conic gradient rotates behind the card and is masked down to the border
ring with mask-composite, so one highlight runs the whole perimeter,
corners included. React Bits' "Star Border" slides two radial gradients
along the top and bottom edges instead, which reads as two streaks passing
rather than a shimmer going around -- the wrong feel for a card, so this is
built differently on purpose. The angle is a registered @Property, since a
bare custom property cannot be interpolated; where that is unsupported the
ring simply sits still.

Both are aria-hidden and cannot take pointer events. The shimmer masks
itself rather than clipping its parent, so the "Best value" badge
overhanging the card is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
# Conflicts:
#	src/pages/pricing/index.astro
@zlshames
zlshames merged commit 9d053d9 into master Sep 18, 2026
1 check passed
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.

1 participant