CLI › Reference

HTML layers in doc.json

The html overlay clip: its fields, the rules the page enforces, and a live layer worked through.

An HTML layer is an overlay clip of kind html. Its source travels in the document, so it is sharp on the cloud, in the studio and in every export, never re-hosted and never counted as an upload.

JSON
{
  "id": "callout",
  "kind": "html",
  "start": 2,
  "duration": 4,
  "html": "<div class=\"c\">Open Code</div>",
  "css": ".c{font-family:'Inter';background:#0b0b0d;color:#fff;padding:20px;border-radius:16px;box-shadow:0 24px 60px rgba(0,0,0,.45)}",
  "box": { "width": 452, "height": 132 },
  "transform": { "x": 0.7, "y": 0.5, "scale": 1, "rotation": 0 }
}

Fields

  • box is the design box in 1080p pixels (the frame is 1920 wide).
  • width, when set, is a fraction of the frame width; absent, the design size is used.
  • bleed is the room around the box for what paints outside it, a shadow or a glow. It is derived from the shadows in the CSS when absent.
  • fonts lists a face outside the hosted catalog: an https URL, or a .woff2 beside the document, which vos push uploads (0.64+). A hosted family needs no entry: name it in the CSS.
  • anim and motion work as on every layer.
  • live: true re-rasterizes the layer per frame; data holds the values {{data.name}} fills. See Live layers.

The three shapes, from the product's register

A callout is unmistakably the video's and still the product's when it shares the product's hue and type and contrasts in value, elevation, scale and place. Three shapes carry that, so a viewer learns them once: a note (a kicker, a title and a line), a tag (one label on the accent) and a code block (the payload). vos callout composes them from the product's own register:

Shell
vos brand https://tweakcn.com --out take/BRAND.md
vos callout take note --step copy-click --kicker "index.css" \
  --title "Every token you tuned, as CSS variables." --mark ring --leader

The register is BRAND.md beside the take (bgA is the ground, accent, fontBody) or --ground #hex --accent #hex --font "…" --body-px 14. The card inverts the app's value in the product's hue (a light app gets a dark card, a dark app a light one), keeps the accent for the kicker, sets its title at 1.35 times the app's body as the camera shows it at the layer's start, lifts on a real shadow and a hairline, rises in and fades out. Its window opens a beat after the step's press and closes before the next scroll or navigation, and it is pinned to the step. --at <s> --seconds <n> places one without a step; --print prints the clip instead of writing it.

vos validate take --picture renders the footage under every html layer at its start and reports the colour difference between the card's ground and what it covers: a card within a small difference of the app reads as one more panel, and the validator says so. An unpinned layer whose window holds a step with an element gets the pin named in a warning.

Pinning a layer to what it is about

A callout has a referent. Name it and the layer stays with it:

JSON
{
  "id": "callout",
  "kind": "html",
  "start": 2,
  "duration": 4,
  "html": "<div class=\"c\">Copies the theme as CSS variables</div>",
  "css": ".c{font-family:'Inter';background:#0b0b0d;color:#fff;padding:20px;border-radius:16px}",
  "box": { "width": 452, "height": 96 },
  "transform": { "x": 0.7, "y": 0.5, "scale": 1, "rotation": 0 },
  "pin": { "step": "copy", "mark": "ring", "leader": true }
}

pin names the referent by one of three things: step, a recorder step's id from actions.json (hover, click, type and drag steps carry the element's rect); press, a source second, for a human recording that has no script (the nearest press names its element); or rect, fractions of the video frame. The layer is placed a gap off one side of the referent (side is auto, the first of right, left, below and above with room inside the frame through the layer's life, or a side by name; gap is design pixels) and carried with the referent as the camera zooms and pans. The layer keeps its screen size; only its place follows. mark draws a ring around the referent or an underline beneath it for the layer's life, leader a hairline from the layer to the referent, color their ink. transform stays the fallback for a pin that cannot resolve, and vos validate says why: an unknown step, a step that touched nothing, a scroll inside the layer's window, a frame with no room beside the referent. In the studio a pinned layer says what it is pinned to in its Layout group; dragging it, or pressing Unpin, gives its place back to the fields.

The rules the page enforces

  • The markup is XML, not HTML: <br /> closes itself, &amp; is written out, attribute values are quoted. Malformed markup decodes to nothing, so vos validate refuses it in words before it ships.
  • The layer fetches nothing on its own. An image the source names on assets.vos.so (the platform's own catalogs) is inlined the way the faces are; an image at any other address paints blank, a file of your own on vos.so included, so inline it as a data: URI. The validator names the host.
  • The CSS is the chrome. shadow, radius, border, frame and key are refused on an html clip.
  • A still layer's CSS animation runs on the wall clock, and the validator warns; make the layer live instead.

A live layer

JSON
{
  "id": "progress",
  "kind": "html",
  "start": 2,
  "duration": 4,
  "live": true,
  "data": { "label": "Rendering" },
  "html": "<div class=\"w\"><span>{{data.label}}</span><span>{{t}} s</span><div class=\"bar\"><i></i></div></div>",
  "css": ".bar{height:10px;background:#222}.bar i{display:block;height:100%;width:0;background:#4ade80;animation:fill 4s linear forwards}@keyframes fill{from{width:0}to{width:100%}}",
  "box": { "width": 520, "height": 110 },
  "transform": { "x": 0.5, "y": 0.5, "scale": 1, "rotation": 0 }
}

{{t}} is the raw number of seconds, rounded to three decimals, with trailing zeros dropped: it reads 2, then 2.6, then 2.633, and a bare 1 on every whole second. For a readout that holds its shape, build the digits from --vos-t, which the page writes on a live layer, and give it font-variant-numeric: tabular-nums:

Text
.n::after{
  counter-reset:
    o round(down, var(--vos-t, 0), 1)
    d calc(round(down, calc(var(--vos-t, 0) * 10), 1)
         - round(down, var(--vos-t, 0), 1) * 10);
  content: counter(o) '.' counter(d) ' s';
}

Keep the , 0 fallback, or the declaration is invalid wherever the layer is not live.

vos validate runs the layer's gate and says in words what would not paint. To look at one moment: vos frames take --at 3.