CineCode
How-to Guides

Highlight and focus code

Spotlight lines, dim others, underline, surround, and point at code to direct attention.

Goal

Direct the viewer's eye to exactly the right part of a code block.

All of these methods are called on a code node returned by s.code(...), and all line numbers are 1-based.

Spotlight a line

Raise one line to full brightness and dim the rest. spotlight is dim-only โ€” it focuses a line and fades the others back, the contrast alone carrying the focus (no accent bar):

let code = s.code(read("src/main.rs"), "rust", 96, 160);
code.fade_in(0, 12).typewriter(6, 60);

code.spotlight(3, 0.6, 80, 24); // focus line 3, dim others by 0.6

Call it again to walk focus down the block โ€” each call animates from the last:

code.spotlight(3, 0.6, 80, 24);
code.spotlight(7, 0.6, 120, 24);
code.unfocus(170, 12); // release everyone

Highlight bar

When you want an accent bar behind a line, use highlight(line, start, dur). It returns a chainable Highlight builder so you can style the bar Tailwind/ GPUI-style:

code.highlight(3, 80, 24); // accent bar behind line 3

Layer it with spotlight for the classic "bar + dim" look:

code.highlight(3, 80, 24).rounded(6);
code.spotlight(3, 0.6, 80, 24);

Style the bar

Every setter returns the builder, so they chain. Colors are 0โ€“1 RGBA:

code.highlight(3, 80, 24)
    .bg(0.20, 0.55, 1.0, 0.25) // translucent blue fill (alias: .color(...))
    .rounded(8)                // corner radius (no-arg defaults to 8)
    .opacity(0.4)              // set just the fill alpha, keep the rgb
    .pad(8, 4);                // extra padding around the line (x, y)

.opacity(a) / .alpha(a) on the builder sets only the bar's fill alpha, so you can pick a color with .bg(r, g, b) and dial transparency separately. (The whole node also has its own .opacity(a) โ€” see Code & node animations.)

Borders (per-side, Tailwind/GPUI-style)

code.highlight(3, 80, 24)
    .bg(0.0, 0.0, 0.0, 0.0)        // transparent fill โ€” borders only
    .border_left()                // single accent edge
    .border_color(0.20, 0.55, 1.0)
    .border_dashed();             // .border_solid() / .border_dotted()
MethodEffect
border(w) / border()All four edges (width defaults to 2).
border_top/right/bottom/left()Enable a single edge.
border_x() / border_y()Left+right / top+bottom edges.
border_color(r,g,b[,a])Border color (defaults to the fill).
border_width(w)Set the stroke width.
border_solid/dashed/dotted()Border line style.
no_border()Clear all borders.

Highlight without dimming

To raise the accent bar without dimming the others:

code.highlight(5, 90, 16);

Or invert it โ€” dim everything except a line:

code.dim_others(5, 0.5, 90, 16);

Walk through execution

To make the viewer watch code run, step a cursor through the lines in the order they execute. execution_cursor(line, โ€ฆ) marks the line currently running (accent bar plus dimming the rest); execution_flow([...], start, step) chains those steps for you โ€” including loops, where a line repeats:

let code = s.code(read("src/factorial.rs"), "rust", 110, 130);
code.typewriter(at("in"), sec(1.6));

// Enter, init, loop body x3, then return. The bar slides line-to-line.
code.execution_flow([1, 2, 3, 4, 3, 4, 3, 4, 6, 7], at("run"), ms(420));
code.unfocus(at("end"), ms(500));

Each entry holds for step; repeating a line number (the 3, 4 pairs above) replays the loop body, so control flow reads exactly like a debugger stepping. No other programmatic video tool can do this โ€” it understands your code as lines, not pixels.

Show variable values

Pair the cursor with variable_update(code, line, label, start, dur) โ€” it pops a small value badge to the right of a line, so the viewer watches state change as the code runs. It returns the badge node, so you can fade each one out as the next appears:

// As the loop body (line 4) runs, acc goes 1 โ†’ 2 โ†’ 6.
let v1 = s.variable_update(code, 4, "acc = 1", at("step1"), ms(250));
v1.dissolve(at("step2"), ms(200));
let v2 = s.variable_update(code, 4, "acc = 2", at("step2"), ms(250));
v2.dissolve(at("step3"), ms(200));
s.variable_update(code, 4, "acc = 6", at("step3"), ms(250));
execution_flow walks the cursor through a factorial loop while variable_update badges track acc changing 1 โ†’ 2 โ†’ 6.

Show a diff (watch a refactor)

Visualize a code change like a review: diff(code, added, removed, start, dur) marks added lines green and removed lines red โ€” each gets a subtle full-line tint, an opaque colored gutter stripe, and a +/- sign. Use diff_add / diff_remove for single lines.

let code = s.code(read("src/parse.rs"), "rust", 150, 150);
code.typewriter(at("in"), sec(1.6));

// Replace the unsafe line (2) with the safe one (3).
s.diff(code, [3], [2], at("patch"), ms(450));

The colored gutter stripe carries the diff color unambiguously even over syntax-highlighted text, so green/red read clearly at a glance.

diff() marks the removed unwrap() line red and the added unwrap_or(0) line green โ€” a one-line refactor, shown like a code review.

Trace a value's flow

token_trace(code, from_line, from_token, to_line, to_token, start, dur) draws an arrow from one token to another โ€” follow a value from its binding to each use. Tokens are matched by substring, so you don't count columns:

// Trace `total` from its definition (line 1) to each use (lines 2, 3).
s.token_trace(code, 1, "total", 2, "total", at("t1"), ms(450));
s.token_trace(code, 1, "total", 3, "total", at("t2"), ms(450));
token_trace follows the value total from its binding to every place it's used.

Move and morph tokens

Transform individual tokens for refactor stories. morph_token swaps a token's text in place (an odometer roll โ€” great for a rename); move_token glides a ghost copy of a token out of the code, leaving its slot empty (extract / hoist). Both occlude the original with a chip matching the code panel, so there's no doubling.

// Rename `vat` -> `tax` in place.
s.morph_token(code, 2, "vat", "tax", at("rename"), ms(500));

// Extract `total` to a callout on the right.
s.move_token(code, 1, "total", 900, 300, at("extract"), ms(650));

These are visual annotations โ€” the code node's text is unchanged. For a real edit (the source string actually changing), use retype / backspace from Edit code live.

morph_token renames vat to tax in place; move_token then glides total out to a hoisted callout.

Underline a line

s.underline(code, 4, 100, 20); // animate an underline under line 4

Surround a range of lines

Draw an animated box around lines fromโ€“to:

s.surround(code, 2, 5, 100, 24);

Point at a line

Drop an arrow that points at a line from a screen position:

// point_at(code, line, from_x, from_y, start, dur)
s.point_at(code, 6, 760, 300, 120, 20);

Gate focus on a marker (after the code finishes typing)

A spotlight reads best after the line it points to is fully on screen. Drive the timing with named markers so the highlight fires exactly when the typewriter completes โ€” no frame math, and it stays correct if you change the typing speed:

mark("type", ms(260));                  // typewriter starts
mark("typed", after("type", sec(2.4))); // ...and finishes here

code.typewriter(at("type"), sec(2.4));

// Spotlight + camera only once the code is fully rendered.
code.highlight(5, at("typed"), ms(450)).rounded(8);
code.spotlight(5, 0.55, at("typed"), ms(450));

let cam = s.camera();
cam.focus_on(code, at("typed"), ms(700), 1.4, 5);

Because typed is defined as after("type", sec(2.4)) โ€” the same duration the typewriter runs โ€” the focus always lands on the first fully-typed frame.

Markers gate the spotlight: the code types in, and only when it's complete does the camera push in on the highlighted line.

Putting it together

A typical walkthrough types the code, then choreographs focus over it:

let code = s.code(read("src/main.rs"), "rust", 96, 160);
code.font_size(36).fade_in(0, 12).typewriter(6, 70);

s.surround(code, 1, 3, 90, 20);     // box the signature
code.spotlight(2, 0.6, 120, 24);    // focus the key line
s.point_at(code, 2, 760, 320, 150, 18);
code.unfocus(200, 14);
Focus and insert animations guiding the eye through code.
Files that made this video

A scene script that creates insert, shift, and spotlight animations.

codescene.toml
Project manifest: output size, theme, scene order, transitions, and shader names.
Open full file
scenes = ["scenes/intro.ccs", "scenes/terrain.ccs", "scenes/pathfind.ccs", "scenes/particles.ccs", "scenes/graph.ccs", "scenes/life.ccs", "scenes/boids.ccs", "scenes/tweens.ccs", "scenes/inserts.ccs", "scenes/edit.ccs", "scenes/edit_source.ccs", "scenes/editor_theme.ccs", "scenes/font_set.ccs", "scenes/remove_styles.ccs", "scenes/morph.ccs", "scenes/shapes.ccs", "scenes/effects.ccs"]
theme = "dark_documentary"

[episode]
title = "How Rivers Form"

[output]
fps = 30
width = 1280
height = 720

[shaders]
aurora = "shaders/aurora.wgsl"
glitch = "shaders/glitch.wgsl"
scanlines = "shaders/scanlines.wgsl"

[transition]
kind = "crossfade"
frames = 18

Reference

The complete list of focus, style, and shape methods on a node is in Code & node animations.

On this page