The page
The page contract. One HTML file, a duration, a seek, relative assets.
A film is films/<name>/page.html plus its own assets folder. Every path in the page is relative.
The contract
<meta name="duration" content="12.4">sets the length in seconds.- Optional
<meta name="fps">is the rate of your frame tables. The render rate is separate: 60 for a final, 30 for a draft. Frames can be fractional. - Time is the seek. Use CSS
@keyframesorelement.animate()(the renderer setscurrentTime), orwindow.seek(t)in seconds that paints frametas a pure function oft. Use both if you like. - No timers, no state between frames, no unseeded random. See Determinism.
- The renderer sets
<html data-aspect="16:9">, and--vwand--vh(pixel lengths) on:root, andwindow.vawe, before your scripts run. See Aspect ratios. - Audio is
<audio>tags, mixed offline. See Audio. - Every tunable number is a literal in the page: a
[[f, v]]table, a keyframe stop or a:rootcustom property.bin/vawe studio <page>edits these literals in place. <meta name="message">holds the one thing to remember.<meta name="spectacle">holds the second of the one big moment. Put quiet before it. Keep one focal point per frame.
A complete page
<!doctype html>
<meta charset="utf-8">
<meta name="duration" content="4">
<meta name="message" content="one page in">
<style>
body { margin: 0; background: #16151a; color: #fff; font: 700 calc(var(--vh) * 0.1) system-ui; }
h1 { position: absolute; left: 8%; top: 38%; margin: 0; }
#bar { position: absolute; left: 8%; top: 62%; width: 40%; height: calc(var(--vh) * 0.012);
background: #0a87ff; transform-origin: left; transform: scaleX(0); }
</style>
<h1 id="word">One page in</h1>
<div id="bar"></div>
<script>
document.getElementById('word').animate(
[{ transform: 'translateY(0.5em)', opacity: 0 }, { transform: 'none', opacity: 1 }],
{ duration: 500, delay: 300, easing: 'ease-out', fill: 'both' });
window.seek = (t) => {
const p = Math.min(1, Math.max(0, (t - 0.6) / 1.2));
document.getElementById('bar').style.transform = `scaleX(${1 - (1 - p) ** 3})`;
};
</script>The heading moves on a Web Animation. The bar moves in window.seek(t). Both are pure functions of time, so frame 400 never needs frame 399.
A worked page with three.js is films/examples/three-star/page.html. A page with a lens (tilt, depth of field, bloom, aberration) is films/examples/tracking-hud/page.html.
A three.js page with a coupled creature-motion rig, filmed through the lens (palette, halation, depth of field, rack focus), is films/examples/moth-photogram/page.html.
Structure it with the loop
A page starts from a template: bin/vawe new <name> --from prompts/<template>.md. See Prompting.
Waivers
A rule broken for cause is declared in the page, with its reason, and nowhere else. Each check has its own code, scoped to the seconds it covers.
<script type="application/json" id="authoring">
{"allow": ["world-held@3.5-6.2"], "_why": {"world-held@3.5-6.2": "world s4, 2.7 s: the wordmark is the last beat and a caret ticks on it"}}
</script>A reason names where (a world id of the page or a second) and what was measured (a number with its unit). A generic reason ("intentional", "by design", "fine") does not count, and nor does one reason copied to three waivers.
Checks advise. Only determinism, banned items and a reason that does not count refuse.