Guitar Strings
Six strings across the page. Sweep the mouse over them and they bend and twang back. On a phone, swipe sideways along one. Turn the sound on and each pluck plays the next note of a random tune, synthesised in the browser.
Pluck
Sound starts off. Browsers only allow audio after a click.
import { PluckStrings } from "@/components/motion/PluckStrings";
<PluckStrings count={6} height={160} />Brew Cup
A loader made of CSS keyframes. It starts on first paint, before any JavaScript has loaded.
Pour
import { BrewCup } from "@/components/motion/BrewCup";
{/* needs the .brew-cup keyframes from globals.css; change key to replay */}
<BrewCup key={run} />Magnetic
Wrap anything and it leans toward the mouse while hovered, then springs back. Does nothing on touch screens.
Pull
import { Magnetic } from "@/components/motion/Magnetic";
<Magnetic strength={0.35}>
<PillButton tone="tunes">Hover me</PillButton>
</Magnetic>Tilt Card
Tilts in 3D toward the pointer, with a glare that slides across the surface.
Tilt
import { TiltCard } from "@/components/motion/TiltCard";
<TiltCard max={14} className="bg-sun">
<div className="h-64 p-6">Kettle Club</div>
</TiltCard>Cursor Lab
Six custom cursors. Pick one and it applies to this whole page. Mark any element with data-cursor="Label" and the cursor turns into that label while it's over the element.
Pick a cursor
Custom cursors only run with a mouse or trackpad. On touch screens the native behaviour stays as it is.
data-cursor="View"Over this box Leaf Pick turns into the label.import { Cursor } from "@/components/cursor/Cursor";
// once, in your root layout
<Cursor variant="ring" /> // "leaf" | "ring" | "blob" | "trail" | "spark" | "kettle"
// anywhere: the cursor becomes a label over this element
<a href="/work" data-cursor="View">Case study</a>Sticker Wall
Stickers spring in, bob while idle, and snap back home when you let go. Dragging is mouse-only so they never get in the way of scrolling on a phone.
Peel
import { Sticker } from "@/components/motion/Sticker";
<Sticker rotate={-12} delay={0.1}>
<span className="rounded-full border-2 border-ink bg-sun px-5 py-3 shadow-hard">New</span>
</Sticker>Throw Box
Fling things around. They keep their momentum and bounce off the walls. Works with touch too.
Fling
import { ThrowBox } from "@/components/drag/ThrowBox";
<ThrowBox
items={[
{ id: "a", left: "8%", top: "12%", rotate: -8, node: <Badge>Fling me</Badge> },
{ id: "b", left: "55%", top: "8%", node: <Ball /> },
]}
/>Reorder List
Drag a row by its grip to reorder the list. Only the grip starts a drag, so swiping elsewhere still scrolls the page on a phone.
Sort
- Boil the water
- Warm the pot
- Add the leaves
- Steep for three minutes
- Pour and enjoy
import { ReorderList } from "@/components/drag/ReorderList";
const [rows, setRows] = useState([
{ id: "1", label: "Boil the water", tone: "bg-sky" },
{ id: "2", label: "Warm the pot", tone: "bg-sun" },
]);
<ReorderList items={rows} onReorder={setRows} />Swipe Deck
Swipe the top card left or right and it goes to the back of the deck. The buttons do the same for keyboard users.
Swipe
Toy Train
Chugs along in pure CSS.
Pill Button
Floods with colour on hover.
Lag Ring
A cursor that takes its time.
import { SwipeDeck } from "@/components/drag/SwipeDeck";
<SwipeDeck
cards={[{ id: "01", title: "Lag Ring", body: "A cursor that takes its time.", tone: "bg-sun" }]}
onSwipe={(card, dir) => console.log(card.title, dir)}
/>