Gravity Pile

Seeded circles, squares and triangles rain down, bounce off the floor and each other, and settle into a heap, with optional content of your own landing on top

Installation
$ pnpm dlx shadcn@latest add @remocn/gravity-pile

Usage

GravityPile runs a small rigid-body simulation. Bodies spawn in a tall column above the frame, fall under gravity, bounce off a floor at 90% of the frame height and off each other, and stack with contact friction so the heap holds its shape instead of flowing apart. One body is painted in the accent color. Late in the fall the simulation bleeds off energy so the heap comes fully to rest, then it sleeps and nothing creeps.

import { GravityPile } from "@/components/remocn/gravity-pile";
 
export const Backlog = () => (
  <GravityPile count={40} seed={12} accentColor="#3D7BFF" />
);

Every body collides as a circle with a radius between 3.6% and 6.8% of the frame height, so squares and triangles rest on each other a little loosely. colors.shapes cycles through its list for the plain bodies. seed changes sizes, kinds, colors and spawn positions.

Dropping your own content

items turns the last bodies into your own nodes. They spawn highest, so they land last and end up on top of the heap. Each item is centered in a square box the size of its body's diameter, 52 to 98px at 720p, and rotates with it, so round content fits best.

import { Img, staticFile } from "remotion";
import { GravityPile } from "@/components/remocn/gravity-pile";
 
const Logo = ({ src }: { src: string }) => (
  <Img
    src={staticFile(src)}
    style={{ width: "100%", height: "100%", borderRadius: "50%" }}
  />
);
 
export const Integrations = () => (
  <GravityPile
    count={30}
    items={[
      <Logo key="a" src="slack.png" />,
      <Logo key="b" src="github.png" />,
      <Logo key="c" src="linear.png" />,
    ]}
  />
);

count includes the items, and at most count items are used.

Timing

At 30 fps, speed={1} and gravity={1}:

FramesMotion
0–20The first bodies fall into frame and hit the floor
20–46The rest of the column lands, bounces and stacks
46–64The heap loses its remaining energy and comes to rest
64+The heap sleeps and holds still

Weaker gravity slows the rain, so the heap settles later: the settle phase starts at 46 / √gravity, about frame 65 at gravity={0.5}. The simulation records 150 frames, after which the last frame holds.

The simulation is precomputed once per count, seed, gravity, number of items and composition size, and every frame reads it back. Contacts are checked between every pair of bodies, so the cost grows with the square of count; a few dozen bodies is instant, but a few hundred will stall the first render.

Props
PropTypeDefaultDescription
count
number30Number of falling bodies, including items
seed
number7Seeds body sizes, kinds, colors and spawn positions
gravity
number1Gravity multiplier; lower values fall slower and settle later
items
ReactNode[]—Nodes dropped as round bodies; they spawn last and land on top of the heap
colors
Partial<GravityPileColors>{ background: "#111111", shapes: ["#FFFFFF", "#C9C9C9", "#7A7A7A"], accent: "#E5322D", floor: "#FFFFFF" }Background, the palette cycled across shapes, the one accent body and the floor line; merged over the defaults. The flat color props below win over it
accentColor
string"#E5322D"The one accent body; overrides colors.accent
backgroundColor
string"#111111"Background; overrides colors.background
speed
number1Playback multiplier