Docs
Looks, sound and captions
How to change the look and the sound of a video, and how the camera and the captions work.
Which looks are there?
Set the look with "theme" in video.json. The same scenes work in every look. That is because colors and fonts have role names, like accent or display, and each look fills them in its own way.
| Look | What it looks like | Scene change | Music |
|---|---|---|---|
paper |
marker drawings on warm paper | brush | warm |
clean |
flat cards with soft shadows | slide | upbeat |
chalk |
chalk on a green board | brush | calm |
blueprint |
white drawings on blueprint blue | wipe | tech |
midnight |
a dark background with glowing colors | zoom | tech |
editorial |
serif headlines and fine lines on warm paper, like a magazine | fade | calm |
studio |
white cards on a soft light background | fade | upbeat |

init picks studio when you don’t name a look, and a project without a "theme" uses paper. Studio suits products and most topics. Paper is friendly and hand drawn. Chalk suits teaching, clean suits products and business, blueprint suits engineering, midnight suits developer tools, and editorial suits essays, history, money and other thoughtful topics.
What is the studio look?
Studio is a light look, made for products and for most other topics. Its cards, labels and notes follow one size scale with "defaults": "0.6", so a label is never larger than the card it points at. See the defaults of 0.6.
A card with a color gets a band of that color along its top, instead of a tinted fill. A list of two or more items puts each item on a card of its own, numbered unless the list has a bullet. card: false on s.list leaves the cards out.
Studio is the look for a product demo, and the product formats use it too.
How do I make my own look?
Use a custom look when several videos should share your colors, fonts and texture. Put this in acme-look.json in your project:
{
"extends": "clean",
"name": "acme",
"bg": "#fbfaf7",
"ink": "#1d1b16",
"accent": "orange",
"colors": { "orange": "#e8590c" },
"background": "dots",
"transition": "fade",
"music": "calm"
}
Set "theme": "acme-look.json" in video.json. You can also point to a shared file, such as "../brand/acme-look.json". Paths start from the project folder. preview reloads when you edit the look.
extends is required and names a built-in look. Everything else is optional. Nested settings change only the values you list.
| Setting | What it changes |
|---|---|
name |
the look’s name. It defaults to the file name and cannot be a built-in name |
dark |
set true when you give a light look a dark background, so fills and shadows suit it |
bg, ink, muted, surface |
the background, text, quieter text and card colors |
accent |
one named color: red, orange, yellow, green, teal, blue, purple, pink, gray or ink |
colors |
your values for those named colors |
fonts |
display, body, hand and mono roles, such as "body": { "family": "Inter", "weight": 500 } |
background |
paper grain, dots, chalk smudges, grid, glow, studio light from the top left or plain. Each uses your bg |
stroke, radius |
line style and card corners |
transition, music |
the default scene change and music style. Settings in video.json win |
code |
window colors: bg, fg, comment, keyword, string, number, fn, punct, lineNo, highlight, border |
terminal |
bg, fg, prompt, muted, bar |
caption |
bg, fg, active |
Use six-digit hex colors such as "#e8590c". caption.bg and code.highlight also accept rgba(...). Font weights are whole numbers. Families must be built in or loaded through fonts in settings. A look file does not load font files.
stroke can set rough, width, roughness, bowing, hachureGap, fillWeight, fillStyle, texture and glow. rough is a boolean. fillStyle takes solid, hachure, cross-hatch, zigzag or dots, and texture takes chalk. Start by changing a few values from the base look. check warns when ink or muted is hard to read against bg.
How does the camera work?
explainroo films each scene with a camera that follows the narration. It opens on the picture. When a scene starts small, the camera starts close and widens as new things need room. Something new that already fits the shot needs no move. Now and then the camera moves in on what the voice talks about, for example a new card and the one it relates to, when there is time to look at it. Near the end of the scene it pulls back, so the last frame shows the whole idea.
During each scene the camera also pushes in very slowly. The background moves a third as much as the camera, so it looks further away.
While a shot holds, every card and label is either fully in the shot or out of it. Arrows and lines may run off the edge, but their labels stay whole, and so do the labels of charts. With "defaults": "0.5" in video.json, a shot that moves to an element also takes in the arrows that point at it and the marks around it. While the camera moves from one shot to the next, things may pass the edge of the frame for a moment. Shots stay clear of the captions and of the buttons Shorts, TikTok and Reels put over the video.
A line, an arrow or something added inside an element already on screen, like a line in a chart, gets no close-up of its own. Neither does an icon without a label or a box without words, because up close it shows nothing new. Highlighted code lines and terminal commands get close-ups when they are small in the shot. s.focus points the camera at any part of the picture.
When a scene goes on with the picture of the scene before it, the camera stays where it was and the two scenes fade into each other.
Things arrive with a little spring, and cards cast a deeper shadow while they land. The final render adds motion blur to frames where something moves, like a film camera. Drafts skip it, so they stay quick.
To keep a scene still, call s.camera([]) in it, or move the camera yourself with s.camera([...]). camera: false on one element keeps the camera from moving to it. "camera": "still" in video.json turns the camera off for the whole video, and "motionBlur": false turns off the motion blur. check looks at the layout as you wrote it. It also judges text at the size the camera shows it, and warns about text or a shape that a held shot cuts at the edge.
Which size should I use?
Pick the size for where you will publish. You can make wide, tall and square videos from one project. The publishing page lists the sizes and explains how to check each one.
Can a video be quicker?
Yes. "pace" in video.json speeds up the whole video. At 1.2 the voice speaks 20% faster. The pauses, the time between scenes, the scene changes and every animation get 20% shorter. Pictures tied to words follow the voice, and the video keeps its rhythm. The music plays a little faster too. 1 is the normal pace. Between 1.15 and 1.3 works well for social media and for viewers who know the topic. Above 1.4 gets hard to follow. You can also slow a video down, to as low as 0.7.
"speed" is different. It changes only how fast the voice speaks.
Which voices are there?
Kokoro runs on your computer. It has 28 English voices and voices for seven other languages. You can also use a paid voice or your own recording, and German needs one of them. See voices and languages to pick one.
speed changes only the voice. It defaults to 0.9 for Kokoro and 1 for the other sources. ElevenLabs limits speed times pace to 0.7 through 1.2, and Cartesia to 0.6 through 1.5.
How does the music work?
explainroo writes new music for each video, as long as the video. Each look has its own style. You can pick another one with "music", which takes warm, upbeat, calm, tech or playful. "music": { "volume": 0.35 } makes it quieter and keeps the look’s style, and explainroo set videos/dns music.volume 0.35 writes that block for you. "music": false turns it off. The music gets quieter while the voice speaks or a video clip plays sound.
At volume 0.5, the built-in music sits about 12 dB under Kokoro’s voice. verify prints one line about the music, measured in the pauses of the narration, like “music: about 12 dB under the voice”.
Can I use my own music?
Yes. Put a music file you are allowed to use in assets/ and name it in video.json:
"music": { "src": "assets/track.mp3", "volume": 0.5, "credit": "Morning by Example Artist, CC BY 4.0" }
It takes the same file types as a recording, like WAV or MP3. explainroo measures it and brings it to the level of the built-in music, so volume means the same for both, and 0.5 is the default. It is lowered while the voice speaks, and it stays in stereo. Music shorter than the video loops, unless you add "loop": false. Music longer than the video fades out at the end. A file plays at its own speed, so it does not follow pace. Use style or src, not both.
The video doesn’t show credit. It goes into the credits of a package, so put it where viewers will see it, for example in the video’s description.
Soundroo is a free, open source tool where your agent writes original music as a score. I also make it. Its engine renders the score to a WAV file on your computer, with no account and no key. It needs Node 22 or newer. Copy the WAV file into assets/ and use it like any other music file. Soundroo’s music has no singing.
Where do the sound effects come from?
Things make a sound when they come in, and the look picks which one. In paper, a drawn shape makes a marker scribble. In clean, a card pops. Arrows whoosh and list items play rising notes. Numbers tick while they count up, typing clicks, and each scene change has a swipe.
"sfx": "minimal" in video.json keeps only the scene changes and the sounds you add with s.sfx(). "sfx": false turns them all off. s.quiet() turns off the entrance sounds for one scene.
s.sound plays a sound file of your own on a word, like a door or a jingle that the built-in effects don’t have. "sfx": false doesn’t turn it off. See adding a sound.
How do captions work?
Captions show the narration a few words at a time. Each word lights up as it is spoken. They are on by default for vertical, 4:5 and square videos. Set "captions": true or false to choose yourself.
"captions": "bold" shows two to four big words at a time instead, in a band of their own. Words in *stars* in the script get the keyword color, and so do numbers and amounts of money. Bold captions suit short vertical videos. They don’t work for Japanese and Chinese, which have no spaces between words to break at. With "sizes", one size can get bold captions while the wide video keeps none. See settings for one size.
Where they sit depends on the size. On Shorts, TikTok and Reels they sit right above the part the app covers. On 4:5, square and wide videos they sit at the bottom, above the watermark. On a plain 9:16 video they sit about a quarter of the way up. The content area, s.safe, always ends above them. check warns about text and shapes that sit where the captions go.
render also writes .srt and .vtt subtitle files, even when captions are off. Viewers can turn these captions on and off in their player. See publishing a video.
When captions are on, don’t also put the same sentence on screen as text. The viewer would read it twice. check gives a hint when a text shows four or more words in a row that the captions show at that moment.
Can I change the watermark?
Every video has a small “explainroo.com” in the bottom right corner. On Shorts, TikTok and Reels it sits in the top right, because the app covers the bottom. "watermark": false in video.json turns it off. "watermark": "yoursite.com" puts your own text there. A TikTok package never has a watermark, because TikTok’s rules don’t allow apps to add one.
I’d ask you to keep it if you can. It helps other people find this free project, and it is a way to give something back.