Skip to content
react-wrapped

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.

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.

tsx
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" },
  ),
];
KeyTypeWhat it does
keystringThe 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.
labelstringWhat 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?booleanA 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.

  • title is 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:

tsx
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 months too, a short name per month. The big number scene writes them across the top of its grid.
  • To build a CalendarLevels by 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.

tsx
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.

Last updated . Read this page as Markdown.