Scroll Moon
A photoreal moon you fly toward as you scroll: crescent → full, crater-sharp disc → the horizon. Content changes with it.
- Zero dependencies
- React + Tailwind
- Accessible
- Reduced-motion aware
Preview
Moon · Realistic grey moon: crescent to full, then the horizon. theme="moon"
One WebGL2 fragment shader, no textures and no three.js. The moon is generated procedurally on the GPU, so it stays sharp at any size and gains detail as you get closer. Scroll position drives distance, phase and rotation, eased every frame for buttery motion; text sections fade in step by step. Ships as Moon, Blood Moon (lunar eclipse) and Mars.
Installation
$ npx shadcn@latest add https://guetix.com/r/scroll-moon.json
Uses the shadcn CLI. It creates components/guetix/scroll-moon.tsx and installs nothing else.
Usage
example.tsx
Space / science landing pageProduct launch storytellingEclipse or event countdownPortfolio hero
Under the hood
The details that make it feel right, and that a one-line prompt usually misses:
- 01Craters are 3D Worley cells sliced by the sphere: a bowl, a raised rim and a central peak, at four scales.
- 02Lommel-Seeliger lighting (the reason the real full moon looks flat and bright) blended with bump-mapped Lambert, so craters cast long shadows at the terminator.
- 03Level of detail: the smallest crater octaves fade in only when they are bigger than a pixel, so there's no shimmer far away.
- 04Filmic tone-map + dithering to avoid banding in the dark sky; renders only while visible and idles when the scroll settles (Regular mode).
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| sections | { eyebrow?: string; title: string; body?: string }[] | — | Story steps, one per screen. |
| theme | "moon" | "blood" | "mars" | "moon" | Art direction. |
| mode | "contained" | "page" | "contained" | Scroll inside its own box, or use window scroll with a sticky scene (sections × 100vh). |
| animated | boolean | true | false = no rotation, twinkle or scroll easing. |
| className | string | — | Extra classes on the root. |
Accessibility
- The canvas is decorative (`aria-hidden`); all content is real text.
- The scroll area is focusable for keyboard scrolling; the active step has `aria-current`.
- Reduced motion: no easing, rotation or twinkle. No WebGL2: a CSS fallback is shown.