Truchet Flip
Quarter-circle tiles snap a quarter turn as a wave crosses the grid, reconnecting the lines until they settle into rings, a calm field, or a circle
$ pnpm dlx shadcn@latest add @remocn/truchet-flipUsage
TruchetFlip fills the frame with Truchet tiles. Each tile holds two
quarter-circle arcs on opposite corners, and every arc ends on an edge
midpoint, so neighboring tiles join into continuous lines. A wave crosses the
grid, and each tile it reaches snaps a quarter turn with a small overshoot.
The lines reconnect behind the wave, and mazes form and dissolve. Two passes
run through the seeded maze. A final pass then turns only the tiles that
differ from the target, and the pattern settles into a circle at the center,
ringed by concentric wavy loops.
import { Backdrop } from "@/components/remocn/backdrop";
import { TruchetFlip } from "@/components/remocn/truchet-flip";
export const PatternBed = () => (
<Backdrop
fill={{ type: "color", value: "#002fa7" }}
padding={0}
radius={0}
shadow=""
>
<TruchetFlip />
</Backdrop>
);The component paints no background, so it composites over any scene; the backdrop supplies the color. Tile size fits the shorter side of the composition, and a tile corner sits exactly on the frame center, so the rings stay centered at 16:9, 1:1 and 9:16. Everything is flat SVG geometry derived from the Remotion frame and the seed, so seeking and rendering are deterministic.
Timing
At 30 fps, speed={1} and the default two passes:
| Frames | Motion |
|---|---|
| 0–6 | The seeded maze holds still |
| 6–48 | First pass turns every tile a quarter, clockwise, outward from the center |
| 30–72 | Second pass follows and turns every tile back |
| 60–102 | Resolve pass turns only the tiles that differ from the target |
| 102–120 | The resolved pattern holds |
Each turn takes 12 frames. The tile reaches its new angle on frame 4, overshoots by about 6°, and lands exactly on the quarter by frame 12. The wave needs 30 frames to cross the grid, so neighboring tiles start a few frames apart and several are always mid-turn. A title placed over the animation can enter around frame 96, as the last tiles settle.
truchetFlipLength is 102, the frame where the last tile settles with the
default two passes. getTruchetFlipTimeline({ passes }).settled gives that
frame for any pass count. getTruchetFlipDuration({ speed, passes, loop })
returns the preview duration at 30 fps, hold included. Each pass beyond the
first adds 24 frames. With passes={0} the maze resolves directly: the last
tile settles on frame 48 and the preview runs 66 frames. For another frame
rate, multiply by fps / 30 and round up.
Set loop for a background. The resolved pattern holds for 24 frames, then a
return pass turns the changed tiles back to the opening maze. The 168-frame
cycle ends on the opening maze, so it repeats without a seam.
Variations
waveMode="diagonal" sweeps from the top-left corner to the bottom-right, and
waveMode="random" turns tiles in a seeded shuffle. resolve="field" aligns
every tile into parallel wavy diagonals. resolve="silhouette" closes the
tiles inside a centered circle into small rings and wraps the field around
them. variant="filled" draws the arcs as quarter discs, and secondaryColor
colors alternate tiles for a two-tone checker.
<Backdrop
fill={{ type: "color", value: "#f1eee7" }}
padding={0}
radius={0}
shadow=""
>
<TruchetFlip
tiles={14}
weight={0.14}
waveMode="diagonal"
resolve="silhouette"
color="#171717"
secondaryColor="#d97757"
loop
/>
</Backdrop>| Prop | Type | Default | Description |
|---|---|---|---|
tiles | number | 10 | Tiles across the shorter side, clamped to 4–24 and rounded; sets the tile size |
weight | number | 0.2 | Stroke width as a fraction of the tile size, clamped to 0.04–0.34; ignored by the filled variant |
passes | number | 2 | Full passes that turn every tile before the resolve pass, clamped to 0–4 and rounded |
waveMode | "radial" | "diagonal" | "random" | "radial" | Turn order: outward from the center, a top-left to bottom-right sweep, or a seeded shuffle |
resolve | "rings" | "field" | "silhouette" | "rings" | Structure the last pass settles into: concentric loops, parallel diagonals, or small rings inside a centered circle |
variant | "stroke" | "filled" | "stroke" | Arcs as butt-capped strokes, or as filled quarter discs |
color | string | "#ffffff" | Line or disc color |
secondaryColor | string | color | Color of alternate tiles in a checkerboard; matches color for a single tone |
seed | number | 1 | Seeds the opening maze and the random wave order |
speed | number | 1 | Playback multiplier; 0 freezes the opening maze |
loop | boolean | false | Adds a return pass so the cycle ends on its first frame |
className | string | — | Optional class name on the full-frame root |
Mondrian SplitNew
Black lines cut the frame into a De Stijl grid, primaries fill the cells, the grid reflows like rubber, and one cell grows until it is the frame
Trim BurstNew
Short strokes shoot out from a point and vanish in flight, each start chasing its end, to punctuate a click, a landing number or a beat