Scenes
A scene is one beat of the video: something to read, drawn on the frame, with the motion that brings it in. react-wrapped ships eleven of them, enough to tell a whole year. You decide what each one says and in which order they play, and the player does the rest.
- Opening365 cells settling into a year, then the headline.
- Big numberThe number counts while the year fills in under it.
- Big dayOne cell of the year, grown to the size of the frame.
- BarsSeven columns, the busiest one lit.
- ChartA day clock chart inside the chart scene.
- ListThree rows, each measured against the first.
- SplitFour shares of one bar, drawn in a single stroke.
- FactAn easter egg: pixel art, the name, its rarity and one line.
- PersonaPixel art from the centre out, then the name.
- SummaryThe whole year on one card, held for two and a half seconds.
- TrophiesFour trophies found and two still hidden.
Placing a scene
Every scene is a function with the same two arguments: scene(props, { key, label }). The props are what this scene says. The second argument is how the video knows it. The result is a SceneInstance, and a video is an array of them.
import { barsScene, bigNumberScene } from "@react-wrapped/scenes";
export const scenes = [
bigNumberScene(
{
eyebrow: "This year you made",
value: 2052,
title: ["contributions."],
lede: "That's **5.6 a day**.",
counter: "climb",
variant: 0,
},
{ key: "total", label: "Total contributions" },
),
barsScene(
{
eyebrow: "Your week",
title: ["You ship on", "*Fridays.*"],
bars: [
{ label: "Thu", value: 352 },
{ label: "Fri", value: 398, peak: true },
{ label: "Sat", value: 84 },
],
lede: "Weekends took **9%** of your work.",
},
{ key: "week", label: "Your week" },
),
];| Key | Type | What it does |
|---|---|---|
key | string | The scene's name inside this video, unique among its scenes. Hidden scenes, their order, edits and picked transitions are all stored by it, so do not change it between visits. |
label | string | What the timeline and the scene editor call the scene. |
drop? | "common" | "rare" | "epic" | "legendary" | Marks a scene that is this person's own find. The timeline colours its segment with that rarity. |
locked? | boolean | A scene that was assigned, not written: none of its texts can be rewritten. |
Text marks
Scene text is plain strings with two marks, so anyone can edit it without an editor. Wrap words in single stars and they take the palette's accent colour. Wrap them in double stars and they are set bold at full strength.
titleis an array on most scenes: one entry per line on screen. Each line rises out of its own mask."You ship on *Fridays.*"puts the last word in the accent colour."That's **5.6 a day**."makes the number bold.
The calendar
Three scenes draw the year as a grid of cells, the way a contribution graph does: a column per week, seven cells tall. They take it as a CalendarLevels: a level from 0 to 4 for each day, oldest first, and lead, the number of empty slots before the first day. calendarFromCounts makes one from a count per day:
import { calendarFromCounts } from "@react-wrapped/core";
// One count per day, oldest first. The second argument is the date of the first count.
const counts = [0, 3, 12, 7, 0, 0, 5];
export const calendar = calendarFromCounts(counts, new Date("2025-10-11"));- A day with nothing is level 0. The others are levels 1 to 4 by quartiles of the active days, so one huge day does not flatten the rest of the year.
- The date is read in UTC, so the server and the browser agree on the weekday.
- The result has
monthstoo, a short name per month. The big number scene writes them across the top of its grid. - To build a
CalendarLevelsby hand, a level of -1 leaves a slot empty.
Variants and the seed
Some scenes take a variant, a second layout or entrance, so two videos do not look alike. Others pick small things themselves from the video's seed: which side a list slides in from, how a counter counts. To pick your own variants the same way, draw them from createRng(seed). The same seed then always gives the same video, on every replay and in the exported file.
import { createRng } from "@react-wrapped/core";
// One source per video. `fork` gives a part of it its own stream, so adding a choice
// in one scene does not reshuffle the others.
const rng = createRng("ada:2026").fork("director");
export const variant = rng.int(2);
export const eyebrow = rng.pick(["Your biggest day", "The day you went off", "Peak you"]);How long a scene stays
A scene plays its entrance and then holds. The hold is never shorter than its text takes to read at about 240 words a minute, so a long line keeps its scene up longer and you do not set durations by hand. A scene that is too tall for the frame is scaled down as a whole, to 60 percent at most, before anything animates.
A scene of your own
All eleven are made with defineScene, which is exported too. Give it a render and an animate and you get a scene function like the ones on this page: it works with every transition, the timeline and the editor. The custom scene guide builds one from start to finish.
