Build log · Recess Club
Brief
A playful chapter — the third and final of the anthology — rendered as a fictional members' creative club called Recess Club. The aesthetic target was explicitly the opposite of the cinematic chapter: bright palette, elastic motion, interactions that invite fidgeting. The reviewer should click the playful card on the landing page, watch a diamond-shaped clip-path expand, and land somewhere that feels like a publication for people who make things with their hands.
Patterns used
clippath-page-transitions— diamond reveal on entrylooping-word-selector— rotating word in the hero headlineside-nav-wipe-reveal— full-screen navigation with stacked panelsscatter-drag-carousel— the members deckdirectional-underline-hover— every CTA and in-copy link
Type and palette
Space Grotesk for body and UI, Caveat for the display accents. The Caveat choice was load-bearing — it reads like a handwritten note in a margin, which is exactly what the chapter is trying to be. Palette pulled from the chapter wrapper:
[data-chapter="playful"] {
--color-bg: #fff6e5; // creamy paper
--color-fg: #1a1633; // midnight ink
--color-muted: #574d7a; // pencil grey
--color-accent: #ff4f6d; // coral
}Coral only appears for metadata, the rotating word's underline, and the main apply CTA. The members cards are deliberately drawn from a secondary palette that does not include coral — so the coral CTA stays the brightest thing on the page.
Decisions
The hero rotator is click-to-advance, not just autoplay
WordRotator autoplays every 2.6 seconds, but hovering pauses it and clicking advances it
immediately. This is a small thing that matters: readers who want to linger on a specific word
(say, "printers") can, and readers who want to see the range get the autoplay. We also set
role="button" and tabIndex={0} so the same interaction is keyboard-available. aria-live
makes sure the rotation is announced.
The side nav sits on top of every other layer except the transition
The nav's z-index stack is 60 (trigger) / 70 (overlay) / 71 (panels). The clip-path transition
overlay lives at z-index: 101 — deliberately above the side nav — so navigating from within
the nav (e.g., clicking the "← Anthology" item) fires the transition cleanly over the
already-open menu. Without this, the menu's panels would be visible during the nav animation,
which looked like a bug even though it technically wasn't.
Scatter carousel uses GSAP Observer, not Draggable
Observer is ~6 KB gzipped vs. Draggable's ~14 KB. More importantly, Observer gives us raw delta
without the snap-and-bounds scaffolding we don't need. We do the infinite wrap math ourselves
with gsap.utils.wrap; cards are duplicated at mount time so the wrap is seamless. The
scattering on press is a paused timeline that .play()s on onPress and .reverse()s on
onRelease — no duplicate tween declarations, just forward + reverse of the same keyframes.
The members cards use generated gradients, not photos
Every members card uses a radial gradient seeded from the card's palette. The tradeoff: real photos would be more convincing as members; gradients are more honest about this being a showcase. They also degrade much better on slow connections — no layout shift while images load, no per-card CLS cost.
Directional underline comes in both flavours
The UnderlineLink primitive accepts direction="ltr" (the default — wipe in from the left,
wipe out to the right) and direction="rtl" (opposite). We alternate them within sections
partly for visual rhythm and partly because it establishes the visual grammar early: every link
animates, they don't all animate the same way, keep your eyes moving. The pattern is CSS-only;
no JavaScript loads for this interaction.
The playful chapter doesn't use Lenis
The editorial chapter got no smooth scroll (native feel for reading). The cinematic chapter got Lenis (slow cinema-adjacent pacing). The playful chapter's interaction density — scatter drag, word clicking, side nav — wants native scroll-snap-to-momentum behaviour. Lenis smooths that responsiveness out in a way that fights the register. So: native scroll.
Tradeoffs rejected
- Animating the scatter on hover, not press. Too twitchy. The scatter is a response to a deliberate gesture, not a passing mouse.
- A real photo grid in the members section. Seven fictional members require seven commissioned portraits. Out of scope for the anthology; maybe for a real second pass.
- Wiring the "Apply" CTA to a real form. This is a showcase; the CTA exists to demonstrate the coral + underline pairing under load. A functional form is the kind of thing that belongs in a follow-up build, not a pattern anthology.
- Parallax on the hero headline. Tried it. The rotating word plus parallax read as hyperactive. Less is more.
Performance notes
- The hero rotator uses
elastic.out(1, 0.85)— a GSAP signature ease. The rotator animatesyPercenton a pre-stacked list inside a clipped window; no layout thrashing. - Scatter carousel duplicates cards once (14 total DOM nodes for 7 members). GSAP's
quickToisn't needed here because we update positions inside theonDragcallback directly. - Side nav has three absolutely-positioned full-viewport panels. GPU-composited; the cost is
irrelevant. Body scroll is locked via
overflow: hiddenon open, restored on close — without which mobile browsers scrolled the page behind the open menu. - Directional underline is pure CSS transforms. Zero runtime cost.
What I'd change in production
- Commission portraits and typography for the members cards. Gradients are a smart placeholder; the real magic of this chapter would be seven actual members photographed by someone who cares.
- Promote
UnderlineLinkinto a project-wideLinkprimitive across all three chapters so the directional underline becomes the site's default link behaviour. Right now it's scoped to playful only. - Add a genuine form and a Resend / Loops pipeline behind the Apply CTA so the chapter functions as a real application page.
- Wire keyboard navigation into the scatter carousel — left/right arrows to shift cards, Enter to "pin" the centre card. Dragging is delightful but not sufficient.