Riposte, a html editor

I just made my personal loopic replacement public on github.

AI Disclaimer: This was built largely with Claude Code. I’m programming for 40 years and work as a developer for 20 years, so this is the tool I used, don’t like it, don’t use it.

I provide TV graphics at international fencing events. Right now preparing for the world championships next week. So this fits my workflow but I think it’s general enough for anyone to use. If there are features missing it’s because I don’t need them, like freeform drawing of surfaces or masks, I simple don’t have a use case for that. But if people are using it and request a feature I’m open to add it.

Riposte

A local, standalone studio for designing and exporting CasparCG HTML graphic templates. Riposte — the counter-attack that follows a parry.

Riposte is a browser-based motion-graphics editor plus a rendering engine, running entirely on your machine: a local Node server, an editor UI, and a small runtime that plays the graphics on air. Scenes are plain JSON, assets are plain files, and everything lives in a git-friendly project folder — no cloud, no accounts. Its core ideas:

  • The runtime is the product. A small zero-dependency engine renders a scene.json via DOM+CSS and implements the CasparCG template contract (update / play / stop / next / invoke). The editor’s preview canvas runs that exact engine, so editor and on-air output cannot diverge.
  • A project is a set, not a scene. Many scenes share one assets/ folder — swap frame.png once and every scene in the set is rebranded. Assets are plain files, content-hash deduplicated; projects are diffable JSON.
  • Exports stay small. Template shells reference the shared assets folder (with an optional baked single-file fallback for compatibility).

Features

Editor

  • Stage with direct manipulation: drag, multi-select (group move/delete), resize handles, on-stage mask handles, snap-to-grid (center-anchored), ruler guides (lockable, layers snap to them), layer locking, zoom/pan.
  • Timeline: per-layer spans, keyframes with bezier easing, keyframe copy/paste across elements and scenes, layer reordering (drag or Ctrl+arrows), draggable markers with per-marker locks — pause, outro (stop() plays from there), loop, and frame actions (JS).
  • Inspector for every element type: text (incl. tabular numerals / digit boxing for jitter-free clocks), image, image sequence, image loader (runtime-fed image with design-time placeholder), rectangle/ellipse (with dynamic size binding — a bar that follows a text layer’s measured width), nested composition.
  • Components: reusable nested compositions — edit once, every scene that embeds one follows; drag a component from the sidebar onto the stage to embed it. Convert scene ⇄ component, extract a selection into a new component (or move it into an existing one); loop regions inside a nested component keep cycling while the parent scene holds.
  • Scene-level intro/outro presets: named, reusable effects (fade, wipes, slides, an inverted-mask diamond…) played on ADD and STOP around the scene’s own animation — with a visual preset editor previewing live on the real scene, and a stock collection seeded into new sets.
  • Masks per layer, animatable x/y/w/h, rotation, inverted, per-corner radius.
  • Data bindings: update() payload keys map to elements (_name convention, dot paths into nested components), visibility bindings with initial state, per-scene preview data for design-time.
  • Asset management: drag-and-drop import, image sequences auto-grouped (rename, drag to stage as one), hover preview pane (images, sequences, live font samples), usage tracking with one-click delete-unused, per-scene visibility filter for the scene list.
  • Undo/redo, per-file dirty tracking (unsaved scenes marked everywhere, auto-saved before export/deploy), deep links (?set=…&scene=…&frame=…&layer=…), resizable persisted panels.

Export & deploy

  • Incremental export: tiny per-scene HTML shells + one shared assets/ folder and runtime; only changed files are written.
  • Optional PNG→WebP re-encoding on export (cached, lossy/lossless race per image; the set on disk stays PNG) — real-world set: 213 MB → 27 MB.
  • One-click deploy into a CasparCG template directory — additive and incremental, with an optional force-full-redeploy.
  • Mapping-contract check on export/deploy: warns about template keys no controller mapping fills and mappings pointing at removed keys.
  • Baked single-file export as a compatibility fallback (all assets inlined).

Rehearsal without a broadcast rig

  • Virtual CasparCG: the server speaks AMCP on its own ports, so the real production controller connects to Riposte as if it were a Caspar server; a playout page renders what would be on air (main + preview feeds). Doubles as a wire-tap to inspect exactly what a graphic receives.
  • Bench: load any exported scene, inject update() data, step the lifecycle (add/play/next/stop), ?debug=<key> logs element geometry per update.

Automation

  • MCP server: the full authoring surface (~50 tools) — set/scene CRUD and conversion, layers, keyframes, masks, markers, loops, bindings, effect presets, asset management, headless scene/filmstrip rendering, export/deploy, and live bench control. An AI assistant can build a graphic from a blank set to a deployed template, and verify it visually, end-to-end.
  • Importer for Loopic .loo projects (scenes, assets, scripts).

Distribution

  • npm run dist builds a portable zip: bundled server, built editor, start scripts — recipients need only a Node.js LTS install.

Sounds intersecting. Will give it a try, maybe next week.

## 0.7.0 — 2026-08-10

SPX and OGraf export contributed by Markus Nygård (@markusnygard).

### Added

- **SPX export mode**: external-style HTML shells with an embedded

`SPXGCTemplateDefinition` — the SPX controller discovers a template’s fields automatically. A per-scene **DataFields configurator** in the Export dialog controls field types, titles, defaults and order; unconfigured scenes auto-detect their keys (including nested components and visibility switches). Steps map to pause markers.

- **OGraf export mode** (EBU standard): one folder per scene with a manifest + `graphic.mjs` custom element wrapping the real Riposte runtime — WYSIWYG holds in OGraf hosts too. The OGraf step model maps to pause markers; action promises resolve when the playhead actually parks or the outro actually completes; `skipAnimation` is honored. Two **asset layouts**: `shared` (default — one deduplicated `assets/` folder for the whole set, WebP re-encoding applies) or `bundled` (spec-portable, each graphic folder self-contained).

- **Export dialog**: the Export button now opens a dialog — mode and target directory are **per-export overrides** that never touch the set’s saved settings. Empty target = the classic `/export` incremental flow; Deploy remains the route to the CasparCG template dir. Target dirs are remembered per set.

- Runtime contract additions: `stop({ skipAnimation })` and `onPaused`/`onEnded` lifecycle listeners (used by the OGraf bridge; fully backward compatible).

- Sidebar: **duplicate and delete sets** (projects only; duplicates skip regenerable export/cache folders).

- Text: **Auto size** checkbox — with textAlign anchoring the box edge (left/right-aligned autoSize text pins x at the aligned edge, the Loopic/OGraf convention); **per-corner border radius** for text and rectangles.

- Size binding: bind a bar to the **longest text in the scene** (`*`), not just a single source element.

- Loopic import: SVG elements, per-corner border radius, raw-XML resource content.

- Opt-in headless drivers verifying exported SPX/OGraf templates end-to-end in Chromium (`test:spx` / `test:ograf`).

### Fixed

- Loopic import: fully transparent-yellow confusion — `rgb(255,255,0)` backgrounds were dropped as “transparent” on import.

- `/api/set/delete` validates the set name like every other endpoint (no path escapes from `projects/`).

- Naming a layer “reference” marks it as a guide layer automatically.