kyfram docs

Programmatic.

Functions, loops, arrays, and trig mean the .lgv is a program — it computes its own keyframe tables instead of you typing them.

The script is a program

Logos already gives you let, fn, for, arrays, and tables; kyfram adds sin(x), cos(x), and pi() so keyframe values can be computed, not typed. Only the declaration calls — scene(), shape(), text(), group(), camera(), audio() — produce video. Everything else is free computation that feeds them. Pair this page with Scripting for the declarations themselves.

one declaration, N shapes
let LETTERS = ["K", "Y", "F", "R", "A", "M"]
for i, ch in LETTERS {
  shape("l" + toStr(i), "text", table{
    "0": table{content: ch, x: 120 + i * 80, y: 180, size: 64, color: "coral"},
    "2": table{content: ch, x: 120 + i * 80, y: 180, size: 80, color: "coral"},
  })
}

Six letters, one loop. The same idea scales to hundreds of shapes — a letter-by-letter reveal, a row of dots, an entire title sequence — without writing a single table by hand.

Functions — reusable keyframe builders

let name = fn(args) { ... return table{...} } — a function whose body returns a keyframe table is a reusable keyframe builder: pass content, position, size, and times; get a shape()-ready table back. The call lands wherever a table literal would.

makeKf — one builder, many shapes
let makeKf = fn(content, x, y, size, t0, t1) {
  return table{
    str(t0): table{content: content, x: x, y: y, size: size, alpha: 0},
    str(t1): table{content: content, x: x, y: y, size: size, alpha: 1},
  }
}
text("t1", makeKf("hello kyfram", 320, 180, 48, 0, 1))
text("t2", makeKf("reuse your motion", 320, 250, 26, 1, 2))

For loops + arrays

Two loop forms. for i in range(0, n) counts integers (end exclusive); for i, x in arr walks an array with its index. Arrays are [...] — list{...} is not valid. Strings concatenate with +; interpolate with "${x}".

array-driven loop
let PALETTE = ["coral", "gold", "mint", "sky"]
for i, c in PALETTE {
  shape("chip" + toStr(i), "circle", table{
    "0": table{x: 80 + i * 160, y: 180, r: 50, color: c},
    "2": table{x: 80 + i * 160, y: 180, r: 70, color: c},
  })
}

Computed keyframe tables

A table literal isn't the only way to hand shape() its keyframes. Start empty, then assign with array-style keys — the time is a computed string, so a loop lays down any number of keyframes, and the finished table drops straight into the declaration.

data-driven keyframes
let kf = table{}
for i in range(0, 7) {
  let t = i * 0.5
  kf[toStr(t)] = table{x: 320, y: 180 + (i - 3) * 40, r: 26, color: "coral"}
}
shape("bob", "circle", kf)

Times are quoted strings in the table — toStr(t) builds them, the engine sorts them. Note the capital S: lowercase tostr fails with identifier not found. Missing props (like alpha here) still hold their defaults per keyframe.

sin(), cos(), pi()

kyfram binds the trig that Logos's std/math lacks: sin(x), cos(x) in radians, and pi() (3.14159…). Combined with a loop they generate whole sine-wave motions inside the script.

a loop-built sine wave
let kf = table{}
let t0 = 0.0
let period = 3.0
let phase = 0.0
for i in range(0, 10) {
  let t = i * 0.3
  let a = 6.28318 * ((t - t0) / period) + phase   // the angle — compute it first
  kf[toStr(t)] = table{x: 320, y: 240 + 80 * sin(a), r: 26, color: "gold"}
}
shape("wob", "circle", kf)
Note: Deeply nested expressions inside builtin calls can crash the interpreter — compute the angle into a variable first, then call sin(a) / cos(a) with it. Don't inline the whole 6.28318 * ((t - t0) / period) + phase into the call.

Syntax facts

the rules in one block
// line comment
/* block comment */

let kf = table{}
kf["0"] = table{color: "#ffffff"}            // hex colors are quoted strings

for i in range(0, 3) { /* integer args, end exclusive */ }
for i, ch in ["K", "Y", "F"] { /* array loop */ }

let joined = "frame " + toStr(7)             // concat with +
let msg = "at ${x}"                          // interpolation
factdetail
comments// line, /* */ block
hex colorsinside keyframe tables they must be quoted strings — color: "#ffffff"
whilenot supported — only for i in range(0, n) and for i, x in arr
rangeend is exclusive; arguments must be integers
list{...}not valid — use [...] arrays
stringsconcat with +, interpolation "${x}"

Worked: a sine hoop of three circles

A function builds keyframes for one dot on a hoop; the loop spreads three dots a lap's worth of angles apart, each making a full revolution. Angle math happens in variables, cos/sin see only plain values.

examples-like, end to end
scene(3, 30)

// onHoop: one keyframe for a dot riding a hoop of radius R around (cx, cy)
let onHoop = fn(cx, cy, R, a) {
  return table{
    x: cx + R * cos(a),           // a arrives as a plain variable
    y: cy + R * sin(a),
    r: 22,
    color: "coral",
  }
}

for i in range(0, 3) {
  let a = 6.28318 * (i / 3)                    // three dots, evenly spread
  let keys = table{
    "0": onHoop(320, 180, 120, a),
    "3": onHoop(320, 180, 120, a + 6.28318),  // one full lap by t=3
  }
  shape("dot" + toStr(i), "circle", keys)
}

See it run

examples/kyfram_ad.lgv is the flagship — data arrays, helper functions, and loop-built sine waves drive the whole ad. examples/patterns.lgv shows stamp() reusing one motion across shapes; examples/groups.lgv shows group transforms. Run any with kyfram render examples/.lgv.